All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elastos.did.DIDDocument Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2019 Elastos Foundation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package org.elastos.did;

import static com.google.common.base.Preconditions.checkArgument;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URI;
import java.nio.ByteBuffer;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;

import org.elastos.did.backend.DIDBiography;
import org.elastos.did.crypto.Base58;
import org.elastos.did.crypto.Base64;
import org.elastos.did.crypto.EcdsaSigner;
import org.elastos.did.crypto.HDKey;
import org.elastos.did.exception.AlreadySealedException;
import org.elastos.did.exception.AlreadySignedException;
import org.elastos.did.exception.CanNotRemoveEffectiveControllerException;
import org.elastos.did.exception.DIDAlreadyExistException;
import org.elastos.did.exception.DIDBackendException;
import org.elastos.did.exception.DIDControllersChangedException;
import org.elastos.did.exception.DIDDeactivatedException;
import org.elastos.did.exception.DIDException;
import org.elastos.did.exception.DIDExpiredException;
import org.elastos.did.exception.DIDNotFoundException;
import org.elastos.did.exception.DIDNotGenuineException;
import org.elastos.did.exception.DIDNotUpToDateException;
import org.elastos.did.exception.DIDObjectAlreadyExistException;
import org.elastos.did.exception.DIDObjectHasReferenceException;
import org.elastos.did.exception.DIDObjectNotExistException;
import org.elastos.did.exception.DIDResolveException;
import org.elastos.did.exception.DIDStoreException;
import org.elastos.did.exception.DIDSyntaxException;
import org.elastos.did.exception.IllegalUsageException;
import org.elastos.did.exception.InvalidKeyException;
import org.elastos.did.exception.MalformedCredentialException;
import org.elastos.did.exception.MalformedDocumentException;
import org.elastos.did.exception.NoEffectiveControllerException;
import org.elastos.did.exception.NotAttachedWithStoreException;
import org.elastos.did.exception.NotControllerException;
import org.elastos.did.exception.NotCustomizedDIDException;
import org.elastos.did.exception.NotPrimitiveDIDException;
import org.elastos.did.exception.UnknownInternalException;
import org.elastos.did.jwt.JwtBuilder;
import org.elastos.did.jwt.JwtParserBuilder;
import org.elastos.did.jwt.KeyProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.digests.SHA256Digest;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.ser.PropertyFilter;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

/**
 * DID documents contain information associated with a DID.
 * They typically express verification methods, such as cryptographic
 * public keys, and services relevant to interactions with the DID subject.
 *
 * 

* The generic properties supported in a DID document are specified by the * Elastos DID Method Specification. *

*/ @JsonPropertyOrder({ DIDDocument.CONTEXT, DIDDocument.ID, DIDDocument.CONTROLLER, DIDDocument.MULTI_SIGNATURE, DIDDocument.PUBLICKEY, DIDDocument.AUTHENTICATION, DIDDocument.AUTHORIZATION, DIDDocument.VERIFIABLE_CREDENTIAL, DIDDocument.SERVICE, DIDDocument.EXPIRES, DIDDocument.PROOF }) public class DIDDocument extends DIDEntity implements Cloneable { protected final static String CONTEXT = "@context"; protected final static String ID = "id"; protected final static String PUBLICKEY = "publicKey"; protected final static String TYPE = "type"; protected final static String CONTROLLER = "controller"; protected final static String MULTI_SIGNATURE = "multisig"; protected final static String PUBLICKEY_BASE58 = "publicKeyBase58"; protected final static String AUTHENTICATION = "authentication"; protected final static String AUTHORIZATION = "authorization"; protected final static String SERVICE = "service"; protected final static String VERIFIABLE_CREDENTIAL = "verifiableCredential"; protected final static String SERVICE_ENDPOINT = "serviceEndpoint"; protected final static String EXPIRES = "expires"; protected final static String PROOF = "proof"; protected final static String CREATOR = "creator"; protected final static String CREATED = "created"; protected final static String SIGNATURE_VALUE = "signatureValue"; /** * W3C DID context URI. */ public final static String W3C_DID_CONTEXT = "https://www.w3.org/ns/did/v1"; /** * Elastos DID context URI. */ public final static String ELASTOS_DID_CONTEXT = "https://ns.elastos.org/did/v1"; /** * W3C DID security context URI. */ public final static String W3ID_SECURITY_CONTEXT = "https://w3id.org/security/v1"; @JsonProperty(CONTEXT) @JsonInclude(Include.NON_EMPTY) List context; @JsonProperty(ID) private DID subject; @JsonProperty(CONTROLLER) @JsonFormat(with = {JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED} ) @JsonInclude(Include.NON_EMPTY) private List controllers; @JsonProperty(MULTI_SIGNATURE) @JsonInclude(Include.NON_NULL) MultiSignature multisig; @JsonProperty(PUBLICKEY) @JsonInclude(Include.NON_EMPTY) private List _publickeys; @JsonProperty(AUTHENTICATION) @JsonInclude(Include.NON_EMPTY) private List _authentications; @JsonProperty(AUTHORIZATION) @JsonInclude(Include.NON_EMPTY) private List _authorizations; @JsonProperty(VERIFIABLE_CREDENTIAL) @JsonInclude(Include.NON_EMPTY) private List _credentials; @JsonProperty(SERVICE) @JsonInclude(Include.NON_EMPTY) private List _services; @JsonProperty(EXPIRES) @JsonInclude(Include.NON_NULL) private Date expires; @JsonProperty(PROOF) @JsonInclude(Include.NON_EMPTY) @JsonFormat(with = {JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED}) private List _proofs; private Map controllerDocs; private Map publicKeys; private Map authenticationKeys; private Map authorizationKeys; private Map credentials; private Map services; private HashMap proofs; private DID effectiveController; private PublicKey defaultPublicKey; private DIDMetadata metadata; private static final Logger log = LoggerFactory.getLogger(DIDDocument.class); /** * MultiSignature is a digital signature scheme which allows a group of * users to sign a single document. */ public static class MultiSignature { private static final MultiSignature ONE_OF_ONE = new MultiSignature(1, 1); private int m; private int n; /** * Create a MultiSignature instance with given signature specification. * *

* The MultiSignature can be of the m-of-n type where any m private * keys out of a possible n are required to sign/verify the signature. *

* * @param m m required keys * @param n n possible keys */ public MultiSignature(int m, int n) { apply(m, n); } /** * Copy constructor. * * @param ms the source MultiSignature object */ private MultiSignature(MultiSignature ms) { apply(ms.m, ms.n); } /** * Create a MultiSignature instance with given signature specification. * * @param mOfN the string format m-of-n(m:n) */ @JsonCreator public MultiSignature(String mOfN) { if (mOfN == null || mOfN.isEmpty()) throw new IllegalArgumentException("Invalid multisig spec"); String[] mn = mOfN.split(":"); if (mn == null || mn.length != 2) throw new IllegalArgumentException("Invalid multisig spec"); apply(Integer.valueOf(mn[0]), Integer.valueOf(mn[1])); } private void apply(int m, int n) { checkArgument(n > 0, "Invalid multisig spec: n should > 0"); checkArgument(m > 0 && m <= n, "Invalid multisig spec: m should > 0 and <= n"); this.m = m; this.n = n; } /** * Get the m of requested signatures. * * @return m */ public int m() { return m; } /** * Get the n of possible signatures. * * @return m */ public int n() { return n; } /** * Compares this MultiSignature to the specified object. * The result is true if and only if the argument is not null and * is a MultiSignature object that represents the same schema. * * @param obj the object to compare this MultiSignature against * @return true if the given object represents a MultiSignature * equivalent to this object, false otherwise */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj instanceof MultiSignature) { MultiSignature multisig = (MultiSignature)obj; return m == multisig.m && n == multisig.n; } return false; } /** * Return the string representation of this MultiSignature object. * * @return a string representation of this MultiSignature object */ @Override @JsonValue public String toString() { return String.format("%d:%d", m, n); } } /** * PublicKey is used for digital signatures, encryption and * other cryptographic operations, which are the basis for purposes such as * authentication or establishing secure communication with service endpoints. */ @JsonPropertyOrder({ ID, TYPE, CONTROLLER, PUBLICKEY_BASE58 }) @JsonFilter("publicKeyFilter") public static class PublicKey implements DIDObject, Comparable { @JsonProperty(ID) private DIDURL id; @JsonProperty(TYPE) private String type; @JsonProperty(CONTROLLER) private DID controller; @JsonProperty(PUBLICKEY_BASE58) private String keyBase58; /** * Constructs a PublicKey instance with the given values. * * @param id the id of the PublicKey * @param type the key type, default type is "ECDSAsecp256r1" * @param controller the DID who holds the private key * @param keyBase58 the base58 encoded public key */ @JsonCreator protected PublicKey(@JsonProperty(value = ID, required = true) DIDURL id, @JsonProperty(value = TYPE) String type, @JsonProperty(value = CONTROLLER) DID controller, @JsonProperty(value = PUBLICKEY_BASE58, required = true) String keyBase58) { this.id = id; this.type = type != null ? type : Constants.DEFAULT_PUBLICKEY_TYPE; this.controller = controller; this.keyBase58 = keyBase58; } /** * Get the PublicKey id. * * @return the key id */ @Override public DIDURL getId() { return id; } /** * Get the PublicKey type. * * @return the type string */ @Override public String getType() { return type; } /** * Get the controller of this PublicKey. * * @return the controller's DID */ public DID getController() { return controller; } /** * Get the base58 encoded public key string. * * @return the base58 encoded public key */ public String getPublicKeyBase58() { return keyBase58; } /** * Get the raw binary public key bytes. * * @return a bytes array of binary public key */ public byte[] getPublicKeyBytes() { return Base58.decode(keyBase58); } /** * Compares this PublicKey to the specified object. * The result is true if and only if the argument is not null and * is a PublicKey object that represents the same key. * * @param obj the object to compare this PublicKey against * @return true if the given object represents a PublicKey * equivalent to this object, false otherwise */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj instanceof PublicKey) { PublicKey ref = (PublicKey)obj; if (getId().equals(ref.getId()) && getType().equals(ref.getType()) && getController().equals(ref.getController()) && getPublicKeyBase58().equals(ref.getPublicKeyBase58())) return true; } return false; } /** * Compares this PublicKey with the specified PublicKey. * * @param key PublicKey to which this PublicKey is to be compared * @return -1, 0 or 1 as this PublicKey is less than, equal to, * or greater than key */ @Override public int compareTo(PublicKey key) { int rc = id.compareTo(key.id); if (rc != 0) return rc; else rc = keyBase58.compareTo(key.keyBase58); if (rc != 0) return rc; else rc = type.compareTo(key.type); if (rc != 0) return rc; else return controller.compareTo(key.controller); } static PropertyFilter getFilter() { return new DIDPropertyFilter() { @Override protected boolean include(PropertyWriter writer, Object pojo, SerializeContext context) { if (context.isNormalized()) return true; PublicKey pk = (PublicKey)pojo; switch (writer.getName()) { case TYPE: return !(pk.getType().equals(Constants.DEFAULT_PUBLICKEY_TYPE)); case CONTROLLER: return !(pk.getController().equals(context.getDid())); default: return true; } } }; } } @JsonSerialize(using = PublicKeyReference.Serializer.class) @JsonDeserialize(using = PublicKeyReference.Deserializer.class) private static class PublicKeyReference implements Comparable { private DIDURL id; private PublicKey key; protected PublicKeyReference(DIDURL id) { this.id = id; this.key = null; } protected PublicKeyReference(PublicKey key) { this.id = key.getId(); this.key = key; } public boolean isVirtual() { return key == null; } public DIDURL getId() { return id; } public PublicKey getPublicKey() { return key; } protected void update(PublicKey key) { checkArgument(key != null && key.getId().equals(id)); this.id = key.getId(); this.key = key; } @Override public int compareTo(PublicKeyReference ref) { if (key != null && ref.key != null) return key.compareTo(ref.key); else return id.compareTo(ref.id); } static class Serializer extends StdSerializer { private static final long serialVersionUID = -6934608221544406405L; @SuppressWarnings("unused") public Serializer() { this(null); } public Serializer(Class t) { super(t); } @Override public void serialize(PublicKeyReference keyRef, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeObject(keyRef.getId()); } } static class Deserializer extends StdDeserializer { private static final long serialVersionUID = -4252894239212420927L; @SuppressWarnings("unused") public Deserializer() { this(null); } public Deserializer(Class t) { super(t); } @Override public PublicKeyReference deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonToken token = p.getCurrentToken(); if (token.equals(JsonToken.VALUE_STRING)) { DIDURL id = p.readValueAs(DIDURL.class); return new PublicKeyReference(id); } else if (token.equals(JsonToken.START_OBJECT)) { PublicKey key = p.readValueAs(PublicKey.class); return new PublicKeyReference(key); } else throw ctxt.weirdStringException(p.getText(), PublicKey.class, "Invalid public key"); } } } /** * A Service may represent any type of service the subject * wishes to advertise, including decentralized identity management services * for further discovery, authentication, authorization, or interaction. */ @JsonPropertyOrder({ ID, TYPE, SERVICE_ENDPOINT }) public static class Service implements DIDObject { @JsonProperty(ID) private DIDURL id; @JsonProperty(TYPE) private String type; @JsonProperty(SERVICE_ENDPOINT) private String endpoint; private Map properties; /** * Create a Service object with given values. * * @param id the service id * @param type the service type * @param endpoint the service endpoint, could be URL or any application defined address * @param properties a map for the extra properties */ protected Service(DIDURL id, String type, String endpoint, Map properties) { this.id = id; this.type = type; this.endpoint = endpoint; if (properties != null && !properties.isEmpty()) { this.properties = new TreeMap(properties); this.properties.remove(ID); this.properties.remove(TYPE); this.properties.remove(SERVICE_ENDPOINT); } } /** * Create a Service object with given values. * * @param id the service id * @param type the service type * @param endpoint the service endpoint, could be URL or any application defined address */ @JsonCreator protected Service(@JsonProperty(value = ID, required = true) DIDURL id, @JsonProperty(value = TYPE, required = true) String type, @JsonProperty(value = SERVICE_ENDPOINT, required = true) String endpoint) { this(id, type, endpoint, null); } /** * Get the service id. * * @return the identifier */ @Override public DIDURL getId() { return id; } /** * Get the service type. * * @return the type string */ @Override public String getType() { return type; } /** * Get the service endpoint. * * @return the service endpoint */ public String getServiceEndpoint() { return endpoint; } /** * Helper getter method for properties serialization. * NOTICE: Should keep the alphabetic serialization order. * * @return a String to Object map include all application defined * properties */ @JsonAnyGetter @JsonPropertyOrder(alphabetic = true) private Map _getProperties() { return properties; } /** * Helper setter method for properties deserialization. * * @param name the property name * @param value the property value */ @JsonAnySetter private void setProperty(String name, Object value) { if (name.equals(ID) || name.equals(TYPE) || name.equals(SERVICE_ENDPOINT)) return; if (properties == null) properties = new TreeMap(); properties.put(name, value); } /** * Get the extra properties of the service. * * @return a map object contains the extra properties */ public Map getProperties() { // TODO: make it unmodifiable recursively return Collections.unmodifiableMap(properties != null ? properties : Collections.emptyMap()); } } /** * The Proof class represents the proof content of DID Document. */ @JsonPropertyOrder({ TYPE, CREATED, CREATOR, SIGNATURE_VALUE }) @JsonFilter("didDocumentProofFilter") public static class Proof implements Comparable { @JsonProperty(TYPE) private String type; @JsonInclude(Include.NON_NULL) @JsonProperty(CREATED) private Date created; @JsonInclude(Include.NON_NULL) @JsonProperty(CREATOR) private DIDURL creator; @JsonProperty(SIGNATURE_VALUE) private String signature; /** * Constructs a Proof object with the given values. * * @param type the type of Proof * @param created the create time stamp * @param creator the key that sign this proof * @param signature the signature string */ @JsonCreator protected Proof(@JsonProperty(value = TYPE) String type, @JsonProperty(value = CREATED, required = true) Date created, @JsonProperty(value = CREATOR) DIDURL creator, @JsonProperty(value = SIGNATURE_VALUE, required = true) String signature) { this.type = type != null ? type : Constants.DEFAULT_PUBLICKEY_TYPE; this.created = created == null ? null : new Date(created.getTime() / 1000 * 1000); this.creator = creator; this.signature = signature; } /** * Constructs a Proof object with the given values. * * @param creator the key that sign this proof * @param signature the signature string */ protected Proof(DIDURL creator, String signature) { this(null, Calendar.getInstance(Constants.UTC).getTime(), creator, signature); } /** * Get the proof type. * * @return the type string */ public String getType() { return type; } /** * Get the create time of this proof object. * * @return the create time stamp */ public Date getCreated() { return created; } /** * Get the key id that sign this proof object * * @return the key id */ public DIDURL getCreator() { return creator; } /** * Get signature string. * * @return the signature string */ public String getSignature() { return signature; } /** * Compares this Proof with the specified Proof object. * * @param proof Proof to which this Proof is to be compared * @return -1, 0 or 1 as this Proof is less than, equal to, * or greater than proof */ @Override public int compareTo(Proof proof) { int rc = (int)(this.created.getTime() - proof.created.getTime()); if (rc == 0) rc = this.creator.compareTo(proof.creator); return rc; } static PropertyFilter getFilter() { return new DIDPropertyFilter() { @Override protected boolean include(PropertyWriter writer, Object pojo, SerializeContext context) { if (context.isNormalized()) return true; Proof proof = (Proof)pojo; switch (writer.getName()) { case TYPE: return !(proof.getType().equals(Constants.DEFAULT_PUBLICKEY_TYPE)); default: return true; } } }; } } /** * Construct a new DIDDocument object with given subject. * * @param subject the owner of DIDDocument */ @JsonCreator protected DIDDocument(@JsonProperty(value = ID, required = true) DID subject) { this.subject = subject; } /** * Copy constructor. * * @param doc the document be copied * @param withProof copy with the proof or not */ private DIDDocument(DIDDocument doc, boolean withProof) { this.context = doc.context; this.subject = doc.subject; this.controllers = doc.controllers; this.controllerDocs = doc.controllerDocs; this.effectiveController = doc.effectiveController; this.multisig = doc.multisig; this.publicKeys = doc.publicKeys; this.authenticationKeys = doc.authenticationKeys; this.authorizationKeys = doc.authorizationKeys; this._publickeys = doc._publickeys; this._authentications = doc._authentications; this._authorizations = doc._authorizations; this.defaultPublicKey = doc.defaultPublicKey; this.credentials = doc.credentials; this._credentials = doc._credentials; this.services = doc.services; this._services = doc._services; this.expires = doc.expires; if (withProof) { this.proofs = doc.proofs; this._proofs = doc._proofs; } this.metadata = doc.metadata; } /** * Get subject of this DIDDocument. * * @return the DID object */ public DID getSubject() { return subject; } private DIDURL canonicalId(String id) { return DIDURL.valueOf(getSubject(), id); } private DIDURL canonicalId(DIDURL id) { if (id == null || id.getDid() != null) return id; return new DIDURL(getSubject(), id); } private void checkAttachedStore() throws NotAttachedWithStoreException { if (!getMetadata().attachedStore()) throw new NotAttachedWithStoreException(); } private void checkIsPrimitive() throws NotPrimitiveDIDException { if (isCustomizedDid()) throw new NotPrimitiveDIDException(getSubject().toString()); } private void checkIsCustomized() throws NotCustomizedDIDException { if (!isCustomizedDid()) throw new NotCustomizedDIDException(getSubject().toString()); } private void checkHasEffectiveController() throws NoEffectiveControllerException { if (getEffectiveController() == null) throw new NoEffectiveControllerException(getSubject().toString()); } /** * Check if this document is a customized DID document. * * @return true if the document is a customized DID document, false otherwise */ public boolean isCustomizedDid() { return defaultPublicKey == null; } /** * Get contoller's DIDs. * * @return the Controllers DID list or empty list if no controller */ public List getControllers() { return Collections.unmodifiableList(controllers); } /** * Get controller count. * * @return the controller count */ public int getControllerCount() { return controllers.size(); } /** * Get contoller's DID. * * @return the Controller's DID if only has one controller, other wise null */ public DID getController() { return controllers.size() == 1 ? controllers.get(0) : null; } /** * Check if current DID has controller. * * @return true if has, false otherwise */ public boolean hasController() { return !controllers.isEmpty(); } /** * Check if current DID has specific controller. * * @param did the controller's DID that to be test * @return true if has, otherwise false */ public boolean hasController(DID did) { return controllers.contains(did); } /** * Get the specific controller's DID document. * * @param did the controller's DID * @return the DIDDocument object or null if no controller */ public DIDDocument getControllerDocument(DID did) { return controllerDocs.get(did); } /** * Get the current effective controller for the customized DIDDocument. * * @return the effective controller's DID or null if not set */ public DID getEffectiveController() { return effectiveController; } /** * Get the current effective controller's document for the * customized DIDDocument. * * @return the effective controller's document or null if not set */ protected DIDDocument getEffectiveControllerDocument() { return effectiveController == null ? null : getControllerDocument(effectiveController); } /** * Set the current effective controller for the customized DIDDocument. * * @param controller the new effective controller */ public void setEffectiveController(DID controller) { checkIsCustomized(); if (controller == null) { effectiveController = controller; return; } else { if (!hasController(controller)) throw new NotControllerException("Not contoller for target DID"); effectiveController = controller; // attach to the store if necessary DIDDocument doc = getControllerDocument(effectiveController); if (!doc.getMetadata().attachedStore()) doc.getMetadata().attachStore(getMetadata().getStore()); } } /** * Check if this document is a multisig document. * * @return true if the document is a multisig document, false otherwise */ public boolean isMultiSignature() { return multisig != null; } /** * Get the multisig specification of this document. * * @return the MultiSignature specification */ public MultiSignature getMultiSignature() { return multisig; } /** * Get the number of the public keys. * * @return the number of the public keys */ public int getPublicKeyCount() { int count = publicKeys.size(); if (hasController()) { for (DIDDocument doc : controllerDocs.values()) count += doc.getAuthenticationKeyCount(); } return count; } /** * Get all the public keys. * * @return a array of PublicKey object */ public List getPublicKeys() { List pks = new ArrayList(publicKeys.values()); if (hasController()) { for (DIDDocument doc : controllerDocs.values()) pks.addAll(doc.getAuthenticationKeys()); } return Collections.unmodifiableList(pks); } /** * Select the public keys by the specified key id or key type. * * @param id the optional key id * @param type the optional key type * @return an array of the matched public keys */ public List selectPublicKeys(DIDURL id, String type) { checkArgument(id != null || type != null, "Invalid select args"); id = canonicalId(id); List pks = new ArrayList(publicKeys.size()); for (PublicKey pk : publicKeys.values()) { if (id != null && !pk.getId().equals(id)) continue; if (type != null && !pk.getType().equals(type)) continue; pks.add(pk); } if (hasController()) { for (DIDDocument doc : controllerDocs.values()) pks.addAll(doc.selectAuthenticationKeys(id, type)); } return Collections.unmodifiableList(pks); } /** * Select the public keys by the specified key id or key type. * * @param id the optional key id * @param type the optional key type * @return an array of the matched public keys */ public List selectPublicKeys(String id, String type) { return selectPublicKeys(canonicalId(id), type); } /** * Get the specific public key. * * @param id the key id * @return the PublicKey object */ public PublicKey getPublicKey(String id) { return getPublicKey(canonicalId(id)); } /** * Get the specific public key. * * @param id the key id * @return the PublicKey object */ public PublicKey getPublicKey(DIDURL id) { checkArgument(id != null, "Invalid publicKey id"); id = canonicalId(id); PublicKey pk = publicKeys.get(id); if (pk == null && hasController()) { DIDDocument doc = getControllerDocument(id.getDid()); if (doc != null) pk = doc.getAuthenticationKey(id); } return pk; } /** * Check if the specified public key exists. * * @param id the key id * @return the key exists or not */ public boolean hasPublicKey(DIDURL id) { return getPublicKey(id) != null; } /** * Check if the specified public key exists. * * @param id the key id * @return the key exists or not */ public boolean hasPublicKey(String id) { return hasPublicKey(canonicalId(id)); } /** * Check if the specified private key exists. * * @param id the key id * @return the key exists or not * @throws DIDStoreException if an error occurred when accessing the store */ public boolean hasPrivateKey(DIDURL id) throws DIDStoreException { checkArgument(id != null, "Invalid publicKey id"); if (hasPublicKey(id) && getMetadata().attachedStore()) return getMetadata().getStore().containsPrivateKey(id); else return false; } /** * Check if the specified private key exists. * * @param id the key id * @return the key exists or not * @throws DIDStoreException if an error occurred when accessing the store */ public boolean hasPrivateKey(String id) throws DIDStoreException { return hasPrivateKey(canonicalId(id)); } /** * Get default public key id * * @return the default public key id */ public DIDURL getDefaultPublicKeyId() { PublicKey pk = getDefaultPublicKey(); return pk != null ? pk.getId() : null; } /** * Get default public key. * * @return the default public key */ public PublicKey getDefaultPublicKey() { if (defaultPublicKey != null) return defaultPublicKey; if (effectiveController != null) return getControllerDocument(effectiveController).getDefaultPublicKey(); return null; } /** * Get the JCE KeyPair object for the given public key. * * @param id the public key id * @return the JCE KeyPair object */ public KeyPair getKeyPair(DIDURL id) { PublicKey pk; if (id == null) { pk = getDefaultPublicKey(); if (pk == null) throw new NoEffectiveControllerException(getSubject().toString()); } else { pk = getPublicKey(id); if (pk == null) throw new InvalidKeyException(id.toString()); } HDKey key = HDKey.deserialize(HDKey.paddingToExtendedPublicKey( pk.getPublicKeyBytes())); return key.getJCEKeyPair(); } /** * Get the JCE KeyPair object for the given public key. * * @param id the public key id * @return the JCE KeyPair object */ public KeyPair getKeyPair(String id) { return getKeyPair(canonicalId(id)); } /** * Get the JCE KeyPair object for the default public key. * * @return the JCE KeyPair object */ public KeyPair getKeyPair() { return getKeyPair((DIDURL)null); } private KeyPair getKeyPair(DIDURL id, String storepass) throws DIDStoreException { checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkAttachedStore(); if (id == null) { id = getDefaultPublicKeyId(); if (id == null) throw new NoEffectiveControllerException(getSubject().toString()); } else { if (!hasPublicKey(id)) throw new InvalidKeyException(ID.toString()); } if (!getMetadata().getStore().containsPrivateKey(id)) throw new InvalidKeyException("No private key: " + id); HDKey key = HDKey.deserialize(getMetadata().getStore().loadPrivateKey( id, storepass)); return key.getJCEKeyPair(); } /** * Derive a private key from this DID. The new private key will derive from * the default private key of the this DIDDocument. * * @param index the derive index * @param storepass the password for the DIDStore * @return the extended private key format. (the real private key is * 32 bytes long start from position 46) * @throws DIDStoreException if an error occurred when accessing the store */ public String derive(int index, String storepass) throws DIDStoreException { checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkAttachedStore(); checkIsPrimitive(); HDKey key = HDKey.deserialize(getMetadata().getStore().loadPrivateKey( getDefaultPublicKeyId(), storepass)); return key.derive(index).serializeBase58(); } private String mapToDerivePath(String identifier, int securityCode) { byte digest[] = new byte[32]; SHA256Digest sha256 = new SHA256Digest(); byte[] in = identifier.getBytes(); sha256.update(in, 0, in.length); sha256.doFinal(digest, 0); StringBuffer path = new StringBuffer(128); ByteBuffer bb = ByteBuffer.wrap(digest); while (bb.hasRemaining()) { int idx = bb.getInt(); if (idx >= 0) path.append(idx); else path.append(idx & 0x7FFFFFFF).append('H'); path.append('/'); } if (securityCode >= 0) path.append(securityCode); else path.append(securityCode & 0x7FFFFFFF).append('H'); return path.toString(); } /** * Derive a private key from this DID. The new private key will derive from * the default private key of the this DIDDocument. * * @param identifier the identifier string * @param securityCode the security code * @param storepass the password for the DIDStore * @return the extended private key format. (the real private key is * 32 bytes long start from position 46) * @throws DIDStoreException if an error occurred when accessing the store */ public String derive(String identifier, int securityCode, String storepass) throws DIDStoreException { checkArgument(identifier != null && !identifier.isEmpty(), "Invalid identifier"); checkAttachedStore(); checkIsPrimitive(); HDKey key = HDKey.deserialize(getMetadata().getStore().loadPrivateKey( getDefaultPublicKeyId(), storepass)); String path = mapToDerivePath(identifier, securityCode); return key.derive(path).serializeBase58(); } /** * Get the numbers of the authentication keys. * * @return the numbers of authentication keys */ public int getAuthenticationKeyCount() { int count = authenticationKeys.size(); if (hasController()) { for (DIDDocument doc : controllerDocs.values()) count += doc.getAuthenticationKeyCount(); } return count; } /** * Get all the authentication keys. * * @return an array of the authentication keys */ public List getAuthenticationKeys() { List pks = new ArrayList(); pks.addAll(authenticationKeys.values()); if (hasController()) { for (DIDDocument doc : controllerDocs.values()) pks.addAll(doc.getAuthenticationKeys()); } return Collections.unmodifiableList(pks); } /** * Select the authentication keys by the specified key id or key type. * * @param id the optional key id * @param type the optional key type * @return an array of the matched authentication keys */ public List selectAuthenticationKeys(DIDURL id, String type) { checkArgument(id != null || type != null, "Invalid select args"); id = canonicalId(id); List pks = new ArrayList(); for (PublicKey pk : authenticationKeys.values()) { if (id != null && !pk.getId().equals(id)) continue; if (type != null && !pk.getType().equals(type)) continue; pks.add(pk); } if (hasController()) { for (DIDDocument doc : controllerDocs.values()) pks.addAll(doc.selectAuthenticationKeys(id, type)); } return Collections.unmodifiableList(pks); } /** * Select the authentication keys by the specified key id or key type. * * @param id the optional key id * @param type the optional key type * @return an array of the matched authentication keys */ public List selectAuthenticationKeys(String id, String type) { return selectAuthenticationKeys(canonicalId(id), type); } /** * Get authentication key with specified key id. * * @param id the key id * @return the matched authentication key object */ public PublicKey getAuthenticationKey(DIDURL id) { checkArgument(id != null, "Invalid publicKey id"); id = canonicalId(id); PublicKey pk = authenticationKeys.get(id); if (pk != null) return pk; if (hasController()) { DIDDocument doc = controllerDocs.get(id.getDid()); if (doc != null) return doc.getAuthenticationKey(id); } return null; } /** * Get authentication key with specified key id. * * @param id the key id string * @return the matched authentication key object */ public PublicKey getAuthenticationKey(String id) { return getAuthenticationKey(canonicalId(id)); } /** * Check whether the given key is authentication key. * * @param id the key id string * @return true if the key is an authentication key, false otherwise */ public boolean isAuthenticationKey(DIDURL id) { return getAuthenticationKey(id) != null; } /** * Check whether the given key is authentication key. * * @param id the key id string * @return true if the key is an authentication key, false otherwise */ public boolean isAuthenticationKey(String id) { return isAuthenticationKey(canonicalId(id)); } /** * Get the numbers of the authorization keys. * * @return the numbers of the authorization keys */ public int getAuthorizationKeyCount() { return authorizationKeys.size(); } /** * Get all the authorization keys. * * @return an array of authorization keys */ public List getAuthorizationKeys() { List pks = new ArrayList(authorizationKeys.values()); return Collections.unmodifiableList(pks); } /** * Select the authorization keys by the specified key id or key type. * * @param id the optional key id * @param type the optional key type * @return an array of the matched authorization keys */ public List selectAuthorizationKeys(DIDURL id, String type) { checkArgument(id != null || type != null, "Invalid select args"); id = canonicalId(id); List pks = new ArrayList(); for (PublicKey pk : authorizationKeys.values()) { if (id != null && !pk.getId().equals(id)) continue; if (type != null && !pk.getType().equals(type)) continue; pks.add(pk); } return Collections.unmodifiableList(pks); } /** * Select the authorization keys by the specified key id or key type. * * @param id the optional key id * @param type the optional key type * @return an array of the matched authorization keys */ public List selectAuthorizationKeys(String id, String type) { return selectAuthorizationKeys(canonicalId(id), type); } /** * Get the specific authorization key. * * @param id the key id * @return the PublicKey object */ public PublicKey getAuthorizationKey(DIDURL id) { checkArgument(id != null, "Invalid publicKey id"); return authorizationKeys.get(id); } /** * Get the specific authorization key. * * @param id the key id * @return the PublicKey object */ public PublicKey getAuthorizationKey(String id) { return getAuthorizationKey(canonicalId(id)); } /** * Check whether the given key is authorization key. * * @param id the key id string * @return true if the key is an authorization key, false otherwise */ public boolean isAuthorizationKey(DIDURL id) { return getAuthorizationKey(id) != null; } /** * Check whether the given key is authorization key. * * @param id the key id string * @return true if the key is an authorization key, false otherwise */ public boolean isAuthorizationKey(String id) { return isAuthorizationKey(canonicalId(id)); } /** * Get the numbers of the credentials. * * @return the numbers of the credentials */ public int getCredentialCount() { return credentials.size(); } /** * Get all the credential. * * @return an array of the credential objects */ public List getCredentials() { return Collections.unmodifiableList(_credentials); } /** * Select the credentials by the specified id or type. * * @param id the optional credential id * @param type the optional credential type * @return an array of the matched credentials */ public List selectCredentials(DIDURL id, String type) { checkArgument(id != null || type != null, "Invalid select args"); id = canonicalId(id); List vcs = new ArrayList(); for (VerifiableCredential vc : credentials.values()) { if (id != null && !vc.getId().equals(id)) continue; if (type != null && !vc.getType().contains(type)) continue; vcs.add(vc); } return Collections.unmodifiableList(vcs); } /** * Select the credentials by the specified id or type. * * @param id the optional credential id * @param type the optional credential type * @return an array of the matched credentials */ public List selectCredentials(String id, String type) { return selectCredentials(canonicalId(id), type); } /** * Get the specific credential. * * @param id the credential id * @return the credential object */ public VerifiableCredential getCredential(DIDURL id) { checkArgument(id != null, "Invalid Credential id"); return credentials.get(canonicalId(id)); } /** * Get the specific credential. * * @param id the credential id * @return the credential object */ public VerifiableCredential getCredential(String id) { return getCredential(canonicalId(id)); } /** * Get the numbers of the services. * * @return the numbers of the services */ public int getServiceCount() { return services.size(); } /** * Get all the services. * * @return an array of the services */ public List getServices() { return Collections.unmodifiableList(_services); } /** * Select the services by the specified id or type. * * @param id the optional service id * @param type the optional service type * @return an array of the matched services */ public List selectServices(DIDURL id, String type) { checkArgument(id != null || type != null, "Invalid select args"); id = canonicalId(id); List svcs = new ArrayList(); for (Service svc : services.values()) { if (id != null && !svc.getId().equals(id)) continue; if (type != null && !svc.getType().equals(type)) continue; svcs.add(svc); }; return Collections.unmodifiableList(svcs); } /** * Select the services by the specified id or type. * * @param id the optional service id * @param type the optional service type * @return an array of the matched services */ public List selectServices(String id, String type) { return selectServices(canonicalId(id), type); } /** * Get the specific service. * * @param id the service id * @return the service object */ public Service getService(DIDURL id) { checkArgument(id != null, "Invalid service id"); return services.get(canonicalId(id)); } /** * Get the specific service. * * @param id the service id * @return the service object */ public Service getService(String id) { return getService(canonicalId(id)); } /** * Get expire time of this DIDDocument. * * @return the expire time */ public Date getExpires() { return expires; } /** * Get last modified time of this DIDDocument. * * @return the last modified time */ public Date getLastModified() { return getProof().getCreated(); } /** * Get signature of this DIDDocumet. * *

* For single-signed DIDDocument object, the document signature is the * proof's signature. For multi-signed DIDDocument object, the document * signature is the first proof's signature. *

* * @return the signature string */ public String getSignature() { return getProof().getSignature(); } /** * Get the proof object of this DIDDocument. if this document is a * multi-signed document, this method returns the first proof object. * * @return the Proof object */ public Proof getProof() { return _proofs.get(0); } /** * Get all Proof objects. * * @return an array of the Proof objects */ public List getProofs() { return Collections.unmodifiableList(_proofs); } /** * Return the subject DID. * * @return a DID object */ @Override protected DID getSerializeContextDid() { return getSubject(); } /** * Sanitize routine before sealing or after deserialization. * * @throws MalformedDocumentException if the document object is invalid */ @Override protected void sanitize() throws MalformedDocumentException { sanitizeControllers(); sanitizePublickKey(); sanitizeCredential(); sanitizeService(); if (expires == null) throw new MalformedDocumentException("Missing document expires."); sanitizeProof(); } private void sanitizeControllers() throws MalformedDocumentException { if (controllers == null || controllers.isEmpty()) { controllers = Collections.emptyList(); controllerDocs = Collections.emptyMap(); if (multisig != null) throw new MalformedDocumentException("Invalid multisig property"); return; } controllerDocs = new HashMap(); try { for (DID did : controllers) { DIDDocument doc = did.resolve(); if (doc == null) throw new MalformedDocumentException("Can not resolve controller: " + did); controllerDocs.put(did, doc); } } catch (DIDResolveException e) { throw new MalformedDocumentException("Can not resolve the controller's DID"); } if (controllers.size() == 1) { if (multisig != null) throw new MalformedDocumentException("Invalid multisig property"); } else { if (multisig == null) throw new MalformedDocumentException("Missing multisig property"); if (multisig.n() != controllers.size()) throw new MalformedDocumentException("Invalid multisig property"); } Collections.sort(controllers); if (controllers.size() == 1) effectiveController = controllers.get(0); } private void sanitizePublickKey() throws MalformedDocumentException { Map pks = new TreeMap(); if (_publickeys != null && _publickeys.size() > 0) { for (PublicKey pk : _publickeys) { if (pk.getId().getDid() == null) { pk.getId().setDid(getSubject()); } else { if (!pk.getId().getDid().equals(getSubject())) throw new MalformedDocumentException("Invalid public key id: " + pk.getId()); } if (pks.containsKey(pk.getId())) throw new MalformedDocumentException("Public key already exists: " + pk.getId()); if (pk.getPublicKeyBase58().isEmpty()) throw new MalformedDocumentException("Invalid public key base58 value."); if (pk.getType() == null) pk.type = Constants.DEFAULT_PUBLICKEY_TYPE; if (pk.getController() == null) pk.controller = getSubject(); pks.put(pk.getId(), pk); } } if (_authentications != null && _authentications.size() > 0) { authenticationKeys = new TreeMap(); PublicKey pk; for (PublicKeyReference keyRef : _authentications) { if (keyRef.isVirtual()) { if (keyRef.getId().getDid() == null) { keyRef.getId().setDid(getSubject()); } else { if (!keyRef.getId().getDid().equals(getSubject())) throw new MalformedDocumentException("Invalid publicKey id: " + keyRef.getId()); } pk = pks.get(keyRef.getId()); if (pk == null) throw new MalformedDocumentException("Not exists publicKey reference: " + keyRef.getId()); keyRef.update(pk); } else { pk = keyRef.getPublicKey(); if (keyRef.getId().getDid() == null) { keyRef.getId().setDid(getSubject()); } else { if (!keyRef.getId().getDid().equals(getSubject())) throw new MalformedDocumentException("Invalid publicKey id: " + keyRef.getId()); } if (pks.containsKey(pk.getId())) throw new MalformedDocumentException("Public key already exists: " + pk.getId()); if (pk.getPublicKeyBase58().isEmpty()) throw new MalformedDocumentException("Invalid public key base58 value."); if (pk.getType() == null) pk.type = Constants.DEFAULT_PUBLICKEY_TYPE; if (pk.getController() == null) pk.controller = getSubject(); pks.put(pk.getId(), pk); } authenticationKeys.put(pk.getId(), pk); } Collections.sort(_authentications); } else { _authentications = Collections.emptyList(); authenticationKeys = Collections.emptyMap(); } if (_authorizations != null && _authorizations.size() > 0) { authorizationKeys = new TreeMap(); PublicKey pk; for (PublicKeyReference keyRef : _authorizations) { if (keyRef.isVirtual()) { if (keyRef.getId().getDid() == null) { keyRef.getId().setDid(getSubject()); } else { if (!keyRef.getId().getDid().equals(getSubject())) throw new MalformedDocumentException("Invalid publicKey id: " + keyRef.getId()); } pk = pks.get(keyRef.getId()); if (pk == null) throw new MalformedDocumentException("Not exists publicKey reference: " + keyRef.getId()); keyRef.update(pk); } else { pk = keyRef.getPublicKey(); if (keyRef.getId().getDid() == null) { keyRef.getId().setDid(getSubject()); } else { if (!keyRef.getId().getDid().equals(getSubject())) throw new MalformedDocumentException("Invalid publicKey id: " + keyRef.getId()); } if (pks.containsKey(pk.getId())) throw new MalformedDocumentException("Public key already exists: " + pk.getId()); if (pk.getPublicKeyBase58().isEmpty()) throw new MalformedDocumentException("Invalid public key base58 value."); if (pk.getType() == null) pk.type = Constants.DEFAULT_PUBLICKEY_TYPE; if (pk.getController() == null) throw new MalformedDocumentException("Public key missing controller: " + pk.getId()); else { if (pk.getController().equals(getSubject())) throw new MalformedDocumentException("Authorization key with wrong controller: " + pk.getId()); } pks.put(pk.getId(), pk); } authorizationKeys.put(pk.getId(), pk); } Collections.sort(_authorizations); } else { _authorizations = Collections.emptyList(); authorizationKeys = Collections.emptyMap(); } // for customized DID with controller, could be no public keys if (pks.size() > 0) { this.publicKeys = pks; this._publickeys = new ArrayList(pks.values()); } else { this.publicKeys = Collections.emptyMap(); this._publickeys = Collections.emptyList(); } // Find default key for (PublicKey pk : publicKeys.values()) { if (pk.getController().equals(getSubject())) { String address = HDKey.toAddress(pk.getPublicKeyBytes()); if (address.equals(getSubject().getMethodSpecificId())) { defaultPublicKey = pk; if (!authenticationKeys.containsKey(pk.getId())) { if (_authentications.isEmpty()) { _authentications = new ArrayList(); authenticationKeys = new TreeMap(); } _authentications.add(new PublicKeyReference(pk)); authenticationKeys.put(pk.getId(), pk); Collections.sort(_authentications); } break; } } } if (controllers.isEmpty() && defaultPublicKey == null) throw new MalformedDocumentException("Missing default public key."); } private void sanitizeCredential() throws MalformedDocumentException { if (_credentials == null || _credentials.isEmpty()) { _credentials = Collections.emptyList(); credentials = Collections.emptyMap(); return; } Map vcs = new TreeMap(); for (VerifiableCredential vc : _credentials) { if (vc.getId() == null) throw new MalformedDocumentException("Missing credential id."); if (vc.getId().getDid() == null) { vc.getId().setDid(getSubject()); } else { if (!vc.getId().getDid().equals(getSubject())) throw new MalformedDocumentException("Invalid crdential id: " + vc.getId()); } if (vcs.containsKey(vc.getId())) throw new MalformedDocumentException("Credential already exists: " + vc.getId()); if (vc.getSubject().getId() == null) vc.getSubject().setId(getSubject()); try { vc.sanitize(); } catch (DIDSyntaxException e) { throw new MalformedDocumentException("Invalid credential: " + vc.getId(), e); } vcs.put(vc.getId(), vc); } this.credentials = vcs; this._credentials = new ArrayList(credentials.values()); } private void sanitizeService() throws MalformedDocumentException { if (_services == null || _services.isEmpty()) { _services = Collections.emptyList(); services = Collections.emptyMap(); return; } Map svcs = new TreeMap(); for (Service svc : _services) { if (svc.getId().getDid() == null) { svc.getId().setDid(getSubject()); } else { if (!svc.getId().getDid().equals(getSubject())) throw new MalformedDocumentException("Invalid crdential id: " + svc.getId()); } if (svc.getType().isEmpty()) throw new MalformedDocumentException("Invalid service type."); if (svc.getServiceEndpoint() == null || svc.getServiceEndpoint().isEmpty()) throw new MalformedDocumentException("Missing service endpoint."); if (svcs.containsKey(svc.getId())) throw new MalformedDocumentException("Service already exists: " + svc.getId()); svcs.put(svc.getId(), svc); } this.services = svcs; this._services = new ArrayList(svcs.values()); } private void sanitizeProof() throws MalformedDocumentException { if (_proofs == null || _proofs.isEmpty()) throw new MalformedDocumentException("Missing document proof"); this.proofs = new HashMap(); for (Proof proof : _proofs) { if (proof.getCreator() == null) { if (defaultPublicKey != null) proof.creator = defaultPublicKey.getId(); else if (controllers.size() == 1) proof.creator = controllerDocs.get(controllers.get(0)).getDefaultPublicKeyId(); else throw new MalformedDocumentException("Missing creator key"); } else { if (proof.getCreator().getDid() == null) { if (defaultPublicKey != null) proof.getCreator().setDid(getSubject()); else if (controllers.size() == 1) proof.getCreator().setDid(controllers.get(0)); else throw new MalformedDocumentException("Invalid creator key"); } } if (proofs.containsKey(proof.getCreator().getDid())) throw new MalformedDocumentException("Aleady exist proof from " + proof.getCreator().getDid()); proofs.put(proof.getCreator().getDid(), proof); } this._proofs = new ArrayList(proofs.values()); Collections.sort(this._proofs); } /** * Set the metadata object for this DIDDocument. * * @param metadata a DIDMetadata object */ protected void setMetadata(DIDMetadata metadata) { this.metadata = metadata; subject.setMetadata(metadata); } /** * Get the metadata object of this DIDDocument. * * @return the DIDMetadata object */ public synchronized DIDMetadata getMetadata() { if (metadata == null) { /* // This will cause resolve recursively try { DIDDocument resolved = getSubject().resolve(); metadata = resolved != null ? resolved.getMetadata() : new DIDMetadata(getSubject()); } catch (DIDResolveException e) { metadata = new DIDMetadata(getSubject()); } */ metadata = new DIDMetadata(getSubject()); } return metadata; } /** * Whether the document object has metadata attached. * * @return true if has metadata attached, false otherwise */ protected boolean hasMetadata() { if (metadata == null) return false; if (metadata.isEmpty()) return false; return true; } /** * Get the attached DIDStore object. * * @return the DIDStore object if attached with store, null otherwise */ protected DIDStore getStore() { return getMetadata().getStore(); } /** * Check if this DIDDocument is expired. * * @return true if expired, false otherwise */ public boolean isExpired() { Calendar now = Calendar.getInstance(Constants.UTC); Calendar expireDate = Calendar.getInstance(Constants.UTC); expireDate.setTime(expires); return now.after(expireDate); } /** * Check is this DIDDocument is genuine. * * @param listener the listener for the verification events and messages * @return true if genuine, false otherwise */ public boolean isGenuine(VerificationEventListener listener) { // Proofs count should match with multisig int expectedProofs = multisig == null ? 1 : multisig.m(); if (proofs.size() != expectedProofs) { if (listener != null) { listener.failed(this, "%s: proof size not matched with multisig, %d expected, actual is %d", getSubject(), multisig.m(), proofs.size()); listener.failed(this, "%s: is not genuine", getSubject()); } return false; } DIDDocument doc = new DIDDocument(this, false); String json = doc.serialize(true); byte[] digest = EcdsaSigner.sha256Digest(json.getBytes()); // Document should signed(only) by default public key. if (!isCustomizedDid()) { Proof proof = getProof(); // Unsupported public key type; if (!proof.getType().equals(Constants.DEFAULT_PUBLICKEY_TYPE)) { if (listener != null) { listener.failed(this, "%s: key type '%s' for proof is not supported", getSubject(), proof.getType()); listener.failed(this, "%s: is not genuine", getSubject()); } return false; } if (!proof.getCreator().equals(getDefaultPublicKeyId())) { if (listener != null) { listener.failed(this, "%s: key '%s' for proof is not default key", getSubject(), proof.getCreator()); listener.failed(this, "%s: is not genuine", getSubject()); } return false; } boolean result = verifyDigest(proof.getCreator(), proof.getSignature(), digest); if (listener != null) { if (result) { listener.succeeded(this, "%s: is genuine", getSubject()); } else { listener.failed(this, "%s: can not verify the signature", getSubject()); listener.failed(this, "%s: is not genuine", getSubject()); } } return result; } else { for (Proof proof : _proofs) { // Unsupported public key type; if (!proof.getType().equals(Constants.DEFAULT_PUBLICKEY_TYPE)) { if (listener != null) { listener.failed(this, "%s: key type '%s' for proof is not supported", getSubject(), proof.getType()); listener.failed(this, "%s: is not genuine", getSubject()); } return false; } DIDDocument controllerDoc = getControllerDocument(proof.getCreator().getDid()); if (controllerDoc == null) { if (listener != null) { listener.failed(this, "%s: can not resolve the document for controller '%s' to verify the proof", getSubject(), proof.getCreator().getDid()); listener.failed(this, "%s: is not genuine", getSubject()); } return false; } if (!controllerDoc.isGenuine(listener)) { if (listener != null) { listener.failed(this, "%s: controller '%s' is not genuine, failed to verify the proof", getSubject(), proof.getCreator().getDid()); listener.failed(this, "%s: is not genuine", getSubject()); } return false; } if (!proof.getCreator().equals(controllerDoc.getDefaultPublicKeyId())) { if (listener != null) { listener.failed(this, "%s: key '%s' for proof is not default key of '%s'", getSubject(), proof.getCreator(), proof.getCreator().getDid()); listener.failed(this, "%s: is not genuine", getSubject()); } return false; } if (!controllerDoc.verifyDigest(proof.getCreator(), proof.getSignature(), digest)) { if (listener != null) { listener.failed(this, "%s: proof '%s' is invalid, signature mismatch", getSubject(), proof.getCreator()); listener.failed(this, "%s: is not genuine", getSubject()); } return false; } } if (listener != null) listener.succeeded(this, "%s: is genuine", getSubject()); return true; } } /** * Check is this DIDDocument is genuine. * * @return true if genuine, false otherwise */ public boolean isGenuine() { return isGenuine(null); } /** * Check if this DIDDocument is deactivated. * * @return true if deactivated, false otherwise * @throws DIDResolveException if an error occurred when resolving DID */ public boolean isDeactivated() throws DIDResolveException { if (getMetadata().isDeactivated()) return true; DIDBiography bio = DIDBackend.getInstance().resolveDidBiography(getSubject()); if (bio == null) return false; boolean deactivated = bio.getStatus() == DIDBiography.Status.DEACTIVATED; if (deactivated) getMetadata().setDeactivated(deactivated); return deactivated; } /** * Check if this DIDDocument is qualified. It means the proof matched * With the multisig specification. * * @return true if qualified, false otherwise */ public boolean isQualified() { if (_proofs == null || _proofs.isEmpty()) return false; return _proofs.size() == (multisig == null ? 1 : multisig.m()); } /** * Check if this DIDDocument is valid. * * @param listener the listener for the verification events and messages * @return true if valid, false otherwise * @throws DIDResolveException if an error occurred when resolving DID */ public boolean isValid(VerificationEventListener listener) throws DIDResolveException { if (isDeactivated()) { if (listener != null) { listener.failed(this, "%s: is deactivated", getSubject()); listener.failed(this, "%s: is invalid", getSubject()); } return false; } if (isExpired()) { if (listener != null) { listener.failed(this, "%s: is expired", getSubject()); listener.failed(this, "%s: is invalid", getSubject()); } return false; } if (!isGenuine(listener)) { if (listener != null) listener.failed(this, "%s: is invalid", getSubject()); return false; } if (hasController()) { for (DIDDocument doc : controllerDocs.values()) { if (doc.isDeactivated()) { if (listener != null) { listener.failed(this, "%s: controller '%s' is deactivated", getSubject(), doc.getSubject()); listener.failed(this, "%s: is invalid", getSubject()); } return false; } if (!doc.isGenuine(listener)) { if (listener != null) { listener.failed(this, "%s: controller '%s' is not genuine", getSubject(), doc.getSubject()); listener.failed(this, "%s: is invalid", getSubject()); } return false; } } } if (listener != null) listener.succeeded(this, "%s: is valid", getSubject()); return true; } /** * Check if this DIDDocument is valid. * * @return true if valid, false otherwise * @throws DIDResolveException if an error occurred when resolving DID */ public boolean isValid() throws DIDResolveException { return isValid(null); } @Override public DIDDocument clone() { DIDDocument doc = new DIDDocument(subject); doc.context = context; doc.controllers = controllers; doc.controllerDocs = controllerDocs; doc.effectiveController = effectiveController; doc.multisig = multisig; doc.publicKeys = publicKeys; doc._publickeys = _publickeys; doc.authenticationKeys = authenticationKeys; doc._authentications = _authentications; doc.authorizationKeys = authorizationKeys; doc._authorizations = _authorizations; doc.defaultPublicKey = defaultPublicKey; doc.credentials = credentials; doc._credentials = _credentials; doc.services = services; doc._services = _services; doc.expires = expires; doc.proofs = proofs; doc._proofs = _proofs; doc.metadata = getMetadata().clone(); return doc; } // Used for Builder create a writable copy. private DIDDocument copy() { DIDDocument doc = new DIDDocument(subject); doc.context = context == null ? null : new ArrayList(context); doc.controllers = new ArrayList(controllers); doc.controllerDocs = new HashMap(controllerDocs); if (this.multisig != null) doc.multisig = new MultiSignature(this.multisig); doc.publicKeys = new TreeMap(publicKeys); doc.authenticationKeys = new TreeMap(authenticationKeys); doc.authorizationKeys = new TreeMap(authorizationKeys); doc.defaultPublicKey = this.defaultPublicKey; doc.credentials = new TreeMap(credentials); doc.services = new TreeMap(services); doc.expires = expires; doc.proofs = new HashMap(proofs); DIDMetadata metadata = getMetadata().clone(); doc.setMetadata(metadata); return doc; } /** * Get DIDDocument Builder object to editing this DIDDocument. * * @return the Builder object */ public Builder edit() { if (!isCustomizedDid()) { checkAttachedStore(); return new Builder(this); } else { if (getEffectiveController() == null) throw new NoEffectiveControllerException(); return edit(getEffectiveControllerDocument()); } } /** * Get DIDDocument Builder object to editing this DIDDocument. * * @param controller the effective controller to editing a customized DIDDocument * @return the Builder object */ public Builder edit(DIDDocument controller) { checkIsCustomized(); if (!getMetadata().attachedStore() && !controller.getMetadata().attachedStore()) throw new NotAttachedWithStoreException(); if (controller.getMetadata().attachedStore()) getMetadata().attachStore(controller.getMetadata().getStore()); else controller.getMetadata().attachStore(getMetadata().getStore()); if (!hasController(controller.getSubject())) throw new NotControllerException(controller.getSubject().toString()); return new Builder(this, controller); } /** * Sign the data by the specified key. * * @param id the key id to sign the data * @param storepass the password for the DIDStore * @param data the data be signed * @return the signature string * @throws DIDStoreException if an error occurred when accessing the store */ public String sign(DIDURL id, String storepass, byte[] ... data) throws DIDStoreException { checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkArgument(data != null && data.length > 0, "Invalid input data"); checkAttachedStore(); byte[] digest = EcdsaSigner.sha256Digest(data); return signDigest(id, storepass, digest); } /** * Sign the data by the specified key. * * @param id the key id to sign the data * @param storepass the password for the DIDStore * @param data the data be signed * @return the signature string * @throws DIDStoreException if an error occurred when accessing the store */ public String sign(String id, String storepass, byte[] ... data) throws DIDStoreException { return sign(canonicalId(id), storepass, data); } /** * Sign the data using the default key. * * @param storepass the password for the DIDStore * @param data the data be signed * @return the signature string * @throws DIDStoreException if an error occurred when accessing the store */ public String sign(String storepass, byte[] ... data) throws DIDStoreException { return sign((DIDURL)null, storepass, data); } /** * Sign the digest by the specified key. * * @param id the key id to sign the data * @param storepass the password for the DIDStore * @param digest the raw digest to be signed * @return the signature string * @throws DIDStoreException if an error occurred when accessing the store */ public String signDigest(DIDURL id, String storepass, byte[] digest) throws DIDStoreException { checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkArgument(digest != null && digest.length > 0, "Invalid digest"); checkAttachedStore(); PublicKey pk = id != null ? getPublicKey(id) : getDefaultPublicKey(); if (pk == null) { if (id != null) throw new InvalidKeyException(id.toString()); else throw new NoEffectiveControllerException(getSubject().toString()); } return getMetadata().getStore().sign(pk.getId(), storepass, digest); } /** * Sign the digest by the specified key. * * @param id the key id to sign the data * @param storepass the password for the DIDStore * @param digest the raw digest to be signed * @return the signature string * @throws DIDStoreException if an error occurred when accessing the store */ public String signDigest(String id, String storepass, byte[] digest) throws DIDStoreException { return signDigest(canonicalId(id), storepass, digest); } /** * Sign the digest by the default key. * * @param storepass the password for the DIDStore * @param digest the raw digest to be signed * @return the signature string * @throws DIDStoreException if an error occurred when accessing the store */ public String signDigest(String storepass, byte[] digest) throws DIDStoreException { return signDigest((DIDURL)null, storepass, digest); } /** * Verify the signature by the specific key and the data. * * @param id the key id * @param signature the signature string * @param data the data to be checked * @return true if the signature was verified, false otherwise */ public boolean verify(DIDURL id, String signature, byte[] ... data) { checkArgument(signature != null && !signature.isEmpty(), "Invalid signature"); checkArgument(data != null && data.length > 0, "Invalid digest"); byte[] digest = EcdsaSigner.sha256Digest(data); return verifyDigest(id, signature, digest); } /** * Verify the signature by the specific key and the data. * * @param id the key id * @param signature the signature string * @param data the data to be checked * @return true if the signature was verified, false otherwise */ public boolean verify(String id, String signature, byte[] ... data) { return verify(canonicalId(id), signature, data); } /** * Verify the signature by the default key and the data. * * @param signature the signature string * @param data the data to be checked * @return true if the signature was verified, false otherwise */ public boolean verify(String signature, byte[] ... data) { return verify((DIDURL)null, signature, data); } /** * Verify the signature by the specific key and the raw digest. * * @param id the key id * @param signature the signature string * @param digest the raw digest to be checked * @return true if the signature was verified, false otherwise */ public boolean verifyDigest(DIDURL id, String signature, byte[] digest) { checkArgument(signature != null && !signature.isEmpty(), "Invalid signature"); checkArgument(digest != null && digest.length > 0, "Invalid digest"); PublicKey pk = id != null ? getPublicKey(id) : getDefaultPublicKey(); if (pk == null) { if (id != null) throw new InvalidKeyException(id.toString()); else throw new InvalidKeyException("No explicit publicKey"); } byte[] binkey = pk.getPublicKeyBytes(); byte[] sig = Base64.decode(signature, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); return EcdsaSigner.verify(binkey, sig, digest); } /** * Verify the signature by the specific key and the raw digest. * * @param id the key id * @param signature the signature string * @param digest the raw digest to be checked * @return true if the signature was verified, false otherwise */ public boolean verifyDigest(String id, String signature, byte[] digest) { return verifyDigest(canonicalId(id), signature, digest); } /** * Verify the signature by the default key and the raw digest. * * @param signature the signature string * @param digest the raw digest to be checked * @return true if the signature was verified, false otherwise */ public boolean verifyDigest(String signature, byte[] digest) { return verifyDigest((DIDURL)null, signature, digest); } /** * Create a JwtBuilder instance using current DIDDocument. * *

* When the JwtBuilder sign the JWT, the JwtBuilder will lookup the keys * through this DIDDocument object. *

* * @return a JwtBuilder instance */ public JwtBuilder jwtBuilder() { JwtBuilder builder = new JwtBuilder(getSubject().toString(), new KeyProvider() { @Override public java.security.PublicKey getPublicKey(String id) { return getKeyPair(canonicalId(id)).getPublic(); } @Override public PrivateKey getPrivateKey(String id, String storepass) throws DIDStoreException { return getKeyPair(canonicalId(id), storepass).getPrivate(); } }); return builder.setIssuer(getSubject().toString()); } /** * Create a JwtParserBuilder instance using current DIDDocument. * *

* When the JwtParserBuilder verify the JWT, the JwtParserBuilder will * lookup the keys through this DIDDocument object. *

* * @return a JwtParserBuilder instance */ public JwtParserBuilder jwtParserBuilder() { JwtParserBuilder jpb = new JwtParserBuilder(new KeyProvider() { @Override public java.security.PublicKey getPublicKey(String id) { return getKeyPair(canonicalId(id)).getPublic(); } @Override public PrivateKey getPrivateKey(String id, String storepass) { return null; } }); jpb.requireIssuer(getSubject().toString()); return jpb; } /** * Create a new customized DID using current DID as the controller. * * @param did the new customized identifier * @param force do not try to resolving the DID, create the document directly * @param storepass the password for the DIDStore * @return the new created DIDDocument * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public DIDDocument newCustomizedDid(DID did, boolean force, String storepass) throws DIDResolveException, DIDStoreException { return newCustomizedDid(did, null, 1, force, storepass); } /** * Create a new customized DID using current DID as the controller. * * @param did the new customized identifier * @param storepass the password for the DIDStore * @return the new created DIDDocument * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public DIDDocument newCustomizedDid(DID did, String storepass) throws DIDResolveException, DIDStoreException { return newCustomizedDid(did, false, storepass); } /** * Create a new customized DID using current DID as the controller. * * @param did the new customized identifier * @param force do not try to resolving the DID, create the document directly * @param storepass the password for the DIDStore * @return the new created DIDDocument * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public DIDDocument newCustomizedDid(String did, boolean force, String storepass) throws DIDResolveException, DIDStoreException { return newCustomizedDid(DID.valueOf(did), force, storepass); } /** * Create a new customized DID using current DID as the controller. * * @param did the new customized identifier * @param storepass the password for the DIDStore * @return the new created DIDDocument * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public DIDDocument newCustomizedDid(String did, String storepass) throws DIDResolveException, DIDStoreException { return newCustomizedDid(DID.valueOf(did), false, storepass); } /** * Create a new customized DID using current DID as the controller. * * @param did the new customized identifier * @param controllers the other controllers for the new customized DID * @param multisig how many signatures are required * @param force do not try to resolving the DID, create the document directly * @param storepass the password for the DIDStore * @return the new created DIDDocument * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public DIDDocument newCustomizedDid(DID did, DID[] controllers, int multisig, boolean force, String storepass) throws DIDResolveException, DIDStoreException { checkArgument(did != null, "Invalid DID"); checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkAttachedStore(); List ctrls = new ArrayList(); if (controllers != null && controllers.length > 0) { for (DID ctrl : controllers) { if (ctrl.equals(getSubject()) || ctrls.contains(ctrl)) continue; ctrls.add(ctrl); } } checkArgument(multisig >= 0 && multisig <= ctrls.size() + 1, "Invalid multisig"); log.info("Creating new DID {} with controller {}...", did, getSubject()); DIDDocument doc = null; if (!force) { doc = did.resolve(true); if (doc != null) throw new DIDAlreadyExistException(did.toString()); } log.info("Creating new DID {} with controller {}...", did, getSubject()); DIDDocument.Builder db = new DIDDocument.Builder(did, this, getStore()); for (DID ctrl : ctrls) db.addController(ctrl); db.setMultiSignature(multisig); try { doc = db.seal(storepass); getStore().storeDid(doc); return doc; } catch (MalformedDocumentException ignore) { throw new UnknownInternalException(ignore); } } /** * Create a new customized DID using current DID as the controller. * * @param did the new customized identifier * @param controllers the other controllers for the new customized DID * @param multisig how many signatures are required * @param storepass the password for the DIDStore * @return the new created DIDDocument * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public DIDDocument newCustomizedDid(DID did, DID[] controllers, int multisig, String storepass) throws DIDResolveException, DIDStoreException { return newCustomizedDid(did, controllers, multisig, false, storepass); } /** * Create a new customized DID using current DID as the controller. * * @param did the new customized identifier * @param controllers the other controllers for the new customized DID * @param multisig how many signatures are required * @param force do not try to resolving the DID, create the document directly * @param storepass the password for the DIDStore * @return the new created DIDDocument * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public DIDDocument newCustomizedDid(String did, String controllers[], int multisig, boolean force, String storepass) throws DIDResolveException, DIDStoreException { List _controllers = new ArrayList(controllers.length); for (String ctrl : controllers) _controllers.add(new DID(ctrl)); return newCustomizedDid(DID.valueOf(did),_controllers.toArray(new DID[0]), multisig, force, storepass); } /** * Create a new customized DID using current DID as the controller. * * @param did the new customized identifier * @param controllers the other controllers for the new customized DID * @param multisig how many signatures are required * @param storepass the password for the DIDStore * @return the new created DIDDocument * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public DIDDocument newCustomizedDid(String did, String controllers[], int multisig, String storepass) throws DIDResolveException, DIDStoreException { return newCustomizedDid(did, controllers, multisig, false, storepass); } /** * Create a TransferTicket object for the this (customized) DIDDocument. * The document should have an effective controller, otherwise this method * will fail. * * @param to who the did will transfer to * @param storepass the password for the DIDStore * @return the TransferTicket object * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public TransferTicket createTransferTicket(DID to, String storepass) throws DIDResolveException, DIDStoreException { checkArgument(to != null, "Invalid to"); checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkIsCustomized(); checkAttachedStore(); checkHasEffectiveController(); TransferTicket ticket = new TransferTicket(this, to); ticket.seal(getEffectiveControllerDocument(), storepass); return ticket; } /** * Create a TransferTicket object(as a controller of the customized DID). * * @param did the target customized DID to be transfer * @param to who the did will transfer to * @param storepass the password for the DIDStore * @return the TransferTicket object * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public TransferTicket createTransferTicket(DID did, DID to, String storepass) throws DIDResolveException, DIDStoreException { checkArgument(did != null, "Invalid did"); checkArgument(to != null, "Invalid to"); checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkIsPrimitive(); checkAttachedStore(); DIDDocument target = did.resolve(true); if (target == null) throw new DIDNotFoundException(did.toString()); if (target.isDeactivated()) throw new DIDDeactivatedException(did.toString()); if (!target.isCustomizedDid()) throw new NotCustomizedDIDException(did.toString()); if (!target.hasController(getSubject())) throw new NotControllerException(getSubject().toString()); TransferTicket ticket = new TransferTicket(target, to); ticket.seal(this, storepass); return ticket; } /** * Sign and seal the TransferTicket object. The current DID should be * the controller of the DID that the TransferTicket to be transfer. * * @param ticket the TransferTicket object to be sign * @param storepass the password for the DIDStore * @return the signed TransferTicket object * @throws DIDStoreException if an error occurred when accessing the store */ public TransferTicket sign(TransferTicket ticket, String storepass) throws DIDStoreException { checkArgument(ticket != null, "Invalid ticket"); checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkAttachedStore(); ticket.seal(this, storepass); return ticket; } /** * Sign and seal the customized DIDDocument object. The current DID should * be the controller of the target DIDDocument. * * @param doc the customized DIDDocument object to be sign * @param storepass the password for the DIDStore * @return the signed DIDDocument object * @throws DIDStoreException if an error occurred when accessing the store */ public DIDDocument sign(DIDDocument doc, String storepass) throws DIDStoreException { checkArgument(doc != null, "Invalid document"); checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkAttachedStore(); if (!doc.isCustomizedDid()) throw new NotCustomizedDIDException(doc.getSubject().toString()); if (!doc.hasController(getSubject())) throw new NotControllerException(); if (isCustomizedDid()) { if (getEffectiveController() == null) throw new NoEffectiveControllerException(getSubject().toString()); } else { if (!doc.hasController(getSubject())) throw new NotControllerException(getSubject().toString()); } if (doc.proofs.containsKey(getSubject())) throw new AlreadySignedException(getSubject().toString()); Builder builder = doc.edit(this); try { return builder.seal(storepass); } catch (MalformedDocumentException ignore) { throw new UnknownInternalException(ignore); } } /** * Publish a DID transfer transaction for current (customized) DIDDocument. * * @param ticket the TransferTicket for current DID * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(TransferTicket ticket, DIDURL signKey, String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { checkArgument(ticket.isValid(), "Invalid ticket"); checkArgument(ticket.getSubject().equals(getSubject()), "Ticket mismatch with current DID"); checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkIsCustomized(); checkArgument(proofs.containsKey(ticket.getTo()), "Document not signed by: " + ticket.getTo()); checkAttachedStore(); if (signKey == null && getDefaultPublicKeyId() == null) throw new NoEffectiveControllerException(getSubject().toString()); DID did = getSubject(); DIDDocument targetDoc = did.resolve(true); if (targetDoc == null) throw new DIDNotFoundException(did.toString()); if (targetDoc.isDeactivated()) throw new DIDDeactivatedException(did.toString()); if (signKey == null) { signKey = getDefaultPublicKeyId(); } else { if (getAuthenticationKey(signKey) == null) throw new InvalidKeyException(signKey.toString()); } DIDBackend.getInstance().transferDid(this, ticket, signKey, storepass, adapter); } /** * Publish a DID transfer transaction for current (customized) DIDDocument. * * @param ticket the TransferTicket for current DID * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(TransferTicket ticket, DIDURL signKey, String storepass) throws DIDStoreException, DIDBackendException { publish(ticket,signKey, storepass, null); } /** * Publish a DID transfer transaction for current (customized) DIDDocument. * * @param ticket the TransferTicket for current DID * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(TransferTicket ticket, String signKey, String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { publish(ticket, canonicalId(signKey), storepass, adapter); } /** * Publish a DID transfer transaction for current (customized) DIDDocument. * * @param ticket the TransferTicket for current DID * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(TransferTicket ticket, String signKey, String storepass) throws DIDStoreException, DIDBackendException { publish(ticket, canonicalId(signKey), storepass, null); } /** * Publish a DID transfer transaction for current (customized) DIDDocument. * Sign the transaction using the default key. * * @param ticket the TransferTicket for current DID * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(TransferTicket ticket, String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { publish(ticket, (DIDURL)null, storepass, adapter); } /** * Publish a DID transfer transaction for current (customized) DIDDocument. * Sign the transaction using the default key. * * @param ticket the TransferTicket for current DID * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(TransferTicket ticket, String storepass) throws DIDStoreException, DIDBackendException { publish(ticket, (DIDURL)null, storepass, null); } /** * Publish a DID transfer transaction for current (customized) DIDDocument * in asynchronous mode. * * @param ticket the TransferTicket for current DID * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return a new CompletableStage */ public CompletableFuture publishAsync(TransferTicket ticket, DIDURL signKey, String storepass, DIDTransactionAdapter adapter) { CompletableFuture future = CompletableFuture.runAsync(() -> { try { publish(ticket, signKey, storepass, adapter); } catch (DIDException e) { throw new CompletionException(e); } }); return future; } /** * Publish a DID transfer transaction for current (customized) DIDDocument * in asynchronous mode. * * @param ticket the TransferTicket for current DID * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @return a new CompletableStage */ public CompletableFuture publishAsync(TransferTicket ticket, DIDURL signKey, String storepass) { return publishAsync(ticket, signKey, storepass, null); } /** * Publish a DID transfer transaction for current (customized) DIDDocument * in asynchronous mode. * * @param ticket the TransferTicket for current DID * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return a new CompletableStage */ public CompletableFuture publishAsync(TransferTicket ticket, String signKey, String storepass, DIDTransactionAdapter adapter) { return publishAsync(ticket, canonicalId(signKey), storepass, adapter); } /** * Publish a DID transfer transaction for current (customized) DIDDocument * in asynchronous mode. * * @param ticket the TransferTicket for current DID * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @return a new CompletableStage */ public CompletableFuture publishAsync(TransferTicket ticket, String signKey, String storepass) { return publishAsync(ticket, canonicalId(signKey), storepass, null); } /** * Publish a DID transfer transaction for current (customized) DIDDocument * in asynchronous mode. Sign the transaction using the default key. * * @param ticket the TransferTicket for current DID * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return a new CompletableStage */ public CompletableFuture publishAsync(TransferTicket ticket, String storepass, DIDTransactionAdapter adapter) { return publishAsync(ticket, (DIDURL)null, storepass, adapter); } /** * Publish a DID transfer transaction for current (customized) DIDDocument * in asynchronous mode. Sign the transaction using the default key. * * @param ticket the TransferTicket for current DID * @param storepass the password for the DIDStore * @return a new CompletableStage */ public CompletableFuture publishAsync(TransferTicket ticket, String storepass) { return publishAsync(ticket, (DIDURL)null, storepass, null); } /** * Publish a DID transfer transaction for current DIDDocument, * internal use only. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ // TODO: to be remove in the future public void publishUntrusted(DIDURL signKey, String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkIsPrimitive(); checkAttachedStore(); if (signKey == null && getDefaultPublicKeyId() == null) throw new NoEffectiveControllerException(getSubject().toString()); log.info("Publishing untrusted DID {}...", getSubject()); if (!isGenuine()) { log.error("Publish failed because document is not genuine."); throw new DIDNotGenuineException(getSubject().toString()); } if (isDeactivated()) { log.error("Publish failed because DID is deactivated."); throw new DIDDeactivatedException(getSubject().toString()); } if (isExpired()) { log.error("Publish failed because document is expired."); throw new DIDExpiredException(getSubject().toString()); } String lastTxid = null; String resolvedSignature = null; DIDDocument resolvedDoc = DIDBackend.getInstance().resolveUntrustedDid(getSubject(), true); if (resolvedDoc != null) { if (resolvedDoc.isDeactivated()) { getMetadata().setDeactivated(true); log.error("Publish failed because DID is deactivated."); throw new DIDDeactivatedException(getSubject().toString()); } resolvedSignature = resolvedDoc.getProof().getSignature(); lastTxid = resolvedDoc.getMetadata().getTransactionId(); } if (signKey == null) { signKey = getDefaultPublicKeyId(); } else { if (getAuthenticationKey(signKey) == null) throw new InvalidKeyException(signKey.toString()); } if (lastTxid == null || lastTxid.isEmpty()) { log.info("Try to publish[create] {}...", getSubject()); DIDBackend.getInstance().createDid(this, signKey, storepass, adapter); } else { log.info("Try to publish[update] {}...", getSubject()); DIDBackend.getInstance().updateDid(this, lastTxid, signKey, storepass, adapter); } if (resolvedSignature != null ) getMetadata().setPreviousSignature(resolvedSignature); getMetadata().setSignature(getProof().getSignature()); } /** * Publish DID Document to the ID chain. * * @param signKey the key to sign the transaction * @param force if true will ignore the conflict between local copy and * the chain copy; otherwise will check the conflict * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(DIDURL signKey, boolean force, String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkAttachedStore(); if (signKey == null && getDefaultPublicKeyId() == null) throw new NoEffectiveControllerException(getSubject().toString()); log.info("Publishing DID {}, force={}...", getSubject(), force); if (!isGenuine()) { log.error("Publish failed because document is not genuine."); throw new DIDNotGenuineException(getSubject().toString()); } if (isDeactivated()) { log.error("Publish failed because DID is deactivated."); throw new DIDDeactivatedException(getSubject().toString()); } if (isExpired() && !force) { log.error("Publish failed because document is expired."); log.info("You can publish the expired document using force mode."); throw new DIDExpiredException(getSubject().toString()); } String lastTxid = null; String resolvedSignature = null; DIDDocument resolvedDoc = getSubject().resolve(true); if (resolvedDoc != null) { if (resolvedDoc.isDeactivated()) { getMetadata().setDeactivated(true); log.error("Publish failed because DID is deactivated."); throw new DIDDeactivatedException(getSubject().toString()); } if (isCustomizedDid()) { List orgControllers = resolvedDoc.getControllers(); List curControllers = getControllers(); if (!curControllers.equals(orgControllers)) throw new DIDControllersChangedException(); MultiSignature curMultisig = getMultiSignature() == null ? MultiSignature.ONE_OF_ONE : getMultiSignature(); MultiSignature orgMultisig = resolvedDoc.getMultiSignature() == null ? MultiSignature.ONE_OF_ONE : resolvedDoc.getMultiSignature(); if (!curMultisig.equals(orgMultisig)) throw new DIDControllersChangedException(); } resolvedSignature = resolvedDoc.getProof().getSignature(); if (!force) { String localPrevSignature = getMetadata().getPreviousSignature(); String localSignature = getMetadata().getSignature(); if (localPrevSignature == null && localSignature == null) { log.error("Missing signatures information, " + "DID SDK dosen't know how to handle it, " + "use force mode to ignore checks."); throw new DIDNotUpToDateException(getSubject().toString()); } else if (localPrevSignature == null || localSignature == null) { String ls = localPrevSignature != null ? localPrevSignature : localSignature; if (!ls.equals(resolvedSignature)) { log.error("Current copy not based on the lastest on-chain copy, signature mismatch."); throw new DIDNotUpToDateException(getSubject().toString()); } } else { if (!localSignature.equals(resolvedSignature) && !localPrevSignature.equals(resolvedSignature)) { log.error("Current copy not based on the lastest on-chain copy, signature mismatch."); throw new DIDNotUpToDateException(getSubject().toString()); } } } lastTxid = resolvedDoc.getMetadata().getTransactionId(); } if (signKey == null) { signKey = getDefaultPublicKeyId(); } else { if (getAuthenticationKey(signKey) == null) throw new InvalidKeyException(signKey.toString()); } if (lastTxid == null || lastTxid.isEmpty()) { log.info("Try to publish[create] {}...", getSubject()); DIDBackend.getInstance().createDid(this, signKey, storepass, adapter); } else { log.info("Try to publish[update] {}...", getSubject()); DIDBackend.getInstance().updateDid(this, lastTxid, signKey, storepass, adapter); } if (resolvedSignature != null ) getMetadata().setPreviousSignature(resolvedSignature); getMetadata().setSignature(getProof().getSignature()); } /** * Publish DID Document to the ID chain. * * @param signKey the key to sign the transaction * @param force if true will ignore the conflict between local copy and * the chain copy; otherwise will check the conflict * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(DIDURL signKey, boolean force, String storepass) throws DIDStoreException, DIDBackendException { publish(signKey, force, storepass, null); } /** * Publish DID Document to the ID chain. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(DIDURL signKey, String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { publish(signKey, false, storepass, adapter); } /** * Publish DID Document to the ID chain. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(DIDURL signKey, String storepass) throws DIDStoreException, DIDBackendException { publish(signKey, false, storepass, null); } /** * Publish DID Document to the ID chain. * * @param signKey the key to sign the transaction * @param force if true will ignore the conflict between local copy and * the chain copy; otherwise will check the conflict * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(String signKey, boolean force, String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { publish(canonicalId(signKey), force, storepass, adapter); } /** * Publish DID Document to the ID chain. * * @param signKey the key to sign the transaction * @param force if true will ignore the conflict between local copy and * the chain copy; otherwise will check the conflict * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(String signKey, boolean force, String storepass) throws DIDStoreException, DIDBackendException { publish(canonicalId(signKey), force, storepass, null); } /** * Publish DID Document to the ID chain. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(String signKey, String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { publish(canonicalId(signKey), false, storepass, adapter); } /** * Publish DID Document to the ID chain. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(String signKey, String storepass) throws DIDStoreException, DIDBackendException { publish(canonicalId(signKey), false, storepass, null); } /** * Publish DID Document to the ID chain. Sign the transaction using * the default key. * * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { publish((DIDURL)null, false, storepass, adapter); } /** * Publish DID Document to the ID chain. Sign the transaction using * the default key. * * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void publish(String storepass) throws DIDStoreException, DIDBackendException { publish((DIDURL)null, false, storepass, null); } /** * Publish DID Document to the ID chain in asynchronous mode. * * @param signKey the key to sign the transaction * @param force if true will ignore the conflict between local copy and * the chain copy; otherwise will check the conflict * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return the new CompletableStage */ public CompletableFuture publishAsync(DIDURL signKey, boolean force, String storepass, DIDTransactionAdapter adapter) { CompletableFuture future = CompletableFuture.runAsync(() -> { try { publish(signKey, force, storepass, adapter); } catch (DIDException e) { throw new CompletionException(e); } }); return future; } /** * Publish DID Document to the ID chain in asynchronous mode. * * @param signKey the key to sign the transaction * @param force if true will ignore the conflict between local copy and * the chain copy; otherwise will check the conflict * @param storepass the password for the DIDStore * @return the new CompletableStage */ public CompletableFuture publishAsync(DIDURL signKey, boolean force, String storepass) { return publishAsync(signKey, force, storepass, null); } /** * Publish DID Document to the ID chain in asynchronous mode. * * @param signKey the key to sign the transaction * @param force if true will ignore the conflict between local copy and * the chain copy; otherwise will check the conflict * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return the new CompletableStage */ public CompletableFuture publishAsync(String signKey, boolean force, String storepass, DIDTransactionAdapter adapter) { CompletableFuture future = CompletableFuture.runAsync(() -> { try { publish(signKey, force, storepass, adapter); } catch (DIDException e) { throw new CompletionException(e); } }); return future; } /** * Publish DID Document to the ID chain in asynchronous mode. * * @param signKey the key to sign the transaction * @param force if true will ignore the conflict between local copy and * the chain copy; otherwise will check the conflict * @param storepass the password for the DIDStore * @return the new CompletableStage */ public CompletableFuture publishAsync(String signKey, boolean force, String storepass) { return publishAsync(signKey, force, storepass, null); } /** * Publish DID Document to the ID chain in asynchronous mode. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return the new CompletableStage */ public CompletableFuture publishAsync(DIDURL signKey, String storepass, DIDTransactionAdapter adapter) { return publishAsync(signKey, false, storepass, adapter); } /** * Publish DID Document to the ID chain in asynchronous mode. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @return the new CompletableStage */ public CompletableFuture publishAsync(DIDURL signKey, String storepass) { return publishAsync(signKey, false, storepass, null); } /** * Publish DID Document to the ID chain in asynchronous mode. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return the new CompletableStage */ public CompletableFuture publishAsync(String signKey, String storepass, DIDTransactionAdapter adapter) { return publishAsync(signKey, false, storepass, adapter); } /** * Publish DID Document to the ID chain in asynchronous mode. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @return the new CompletableStage */ public CompletableFuture publishAsync(String signKey, String storepass) { return publishAsync(signKey, false, storepass, null); } /** * Publish DID Document to the ID chain in asynchronous mode. Sign the * transaction using the default key. * * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return the new CompletableStage */ public CompletableFuture publishAsync(String storepass, DIDTransactionAdapter adapter) { return publishAsync((DIDURL)null, false, storepass, adapter); } /** * Publish DID Document to the ID chain in asynchronous mode. Sign the * transaction using the default key. * * @param storepass the password for the DIDStore * @return the new CompletableStage */ public CompletableFuture publishAsync(String storepass) { return publishAsync((DIDURL)null, false, storepass, null); } /** * Deactivate this DID using the authentication key. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void deactivate(DIDURL signKey, String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkAttachedStore(); if (signKey == null && getDefaultPublicKeyId() == null) throw new NoEffectiveControllerException(getSubject().toString()); signKey = canonicalId(signKey); // Document should use the IDChain's copy DIDDocument doc = getSubject().resolve(true); if (doc == null) throw new DIDNotFoundException(getSubject().toString()); else if (doc.isDeactivated()) throw new DIDDeactivatedException(getSubject().toString()); else doc.getMetadata().attachStore(getStore()); doc.effectiveController = effectiveController; if (signKey == null) { signKey = doc.getDefaultPublicKeyId(); } else { if (!doc.isCustomizedDid()) { // the signKey should be default key or authorization key if (!doc.getDefaultPublicKeyId().equals(signKey) && doc.getAuthorizationKey(signKey) == null) throw new InvalidKeyException(signKey.toString()); } else { // the signKey should be controller's default key DIDDocument controller = doc.getControllerDocument(signKey.getDid()); if (controller == null || !controller.getDefaultPublicKeyId().equals(signKey)) throw new InvalidKeyException(signKey.toString()); } } DIDBackend.getInstance().deactivateDid(doc, signKey, storepass, adapter); } /** * Deactivate this DID using the authentication key. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void deactivate(DIDURL signKey, String storepass) throws DIDStoreException, DIDBackendException { deactivate(signKey, storepass, null); } /** * Deactivate this DID using the authentication key. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void deactivate(String signKey, String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { deactivate(canonicalId(signKey), storepass, adapter); } /** * Deactivate this DID using the authentication key. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void deactivate(String signKey, String storepass) throws DIDStoreException, DIDBackendException { deactivate(canonicalId(signKey), storepass, null); } /** * Deactivate this DID using the authentication key. Sign the transaction * using the default key. * * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void deactivate(String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { deactivate((DIDURL)null, storepass, adapter); } /** * Deactivate this DID using the authentication key. Sign the transaction * using the default key. * * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void deactivate(String storepass) throws DIDStoreException, DIDBackendException { deactivate((DIDURL)null, storepass, null); } /** * Deactivate this DID using the authentication key in asynchronous mode. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return the new CompletableStage */ public CompletableFuture deactivateAsync(DIDURL signKey, String storepass, DIDTransactionAdapter adapter) { CompletableFuture future = CompletableFuture.runAsync(() -> { try { deactivate(signKey, storepass, adapter); } catch (DIDException e) { throw new CompletionException(e); } }); return future; } /** * Deactivate this DID using the authentication key in asynchronous mode. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @return the new CompletableStage */ public CompletableFuture deactivateAsync(DIDURL signKey, String storepass) { return deactivateAsync(signKey, storepass, null); } /** * Deactivate this DID using the authentication key in asynchronous mode. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return the new CompletableStage */ public CompletableFuture deactivateAsync(String signKey, String storepass, DIDTransactionAdapter adapter) { CompletableFuture future = CompletableFuture.runAsync(() -> { try { deactivate(signKey, storepass, adapter); } catch (DIDException e) { throw new CompletionException(e); } }); return future; } /** * Deactivate this DID using the authentication key in asynchronous mode. * * @param signKey the key to sign the transaction * @param storepass the password for the DIDStore * @return the new CompletableStage */ public CompletableFuture deactivateAsync(String signKey, String storepass) { return deactivateAsync(signKey, storepass, (DIDTransactionAdapter)null); } /** * Deactivate this DID using the authentication key in asynchronous mode. * Sign the transaction using the default key. * * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return the new CompletableStage */ public CompletableFuture deactivateAsync(String storepass, DIDTransactionAdapter adapter) { return deactivateAsync((DIDURL)null, storepass, adapter); } /** * Deactivate this DID using the authentication key in asynchronous mode. * Sign the transaction using the default key. * * @param storepass the password for the DIDStore * @return the new CompletableStage */ public CompletableFuture deactivateAsync(String storepass) { return deactivateAsync((DIDURL)null, storepass, null); } /** * Deactivate the target DID with the authorization. * * @param target the DID to be deactivated * @param signKey the key to sign the transaction, should be * authorized by the target DID * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void deactivate(DID target, DIDURL signKey, String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { checkArgument(target != null, "Invalid target DID"); checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); checkAttachedStore(); if (signKey == null && getDefaultPublicKeyId() == null) throw new NoEffectiveControllerException(getSubject().toString()); DIDDocument targetDoc = target.resolve(true); if (targetDoc == null) throw new DIDNotFoundException(target.toString()); else if (targetDoc.isDeactivated()) throw new DIDDeactivatedException(target.toString()); targetDoc.getMetadata().attachStore(getStore()); if (!targetDoc.isCustomizedDid()) { if (targetDoc.getAuthorizationKeyCount() == 0) throw new InvalidKeyException("No authorization key from: " + target); List candidatePks = null; if (signKey == null) { candidatePks = this.getAuthenticationKeys(); } else { PublicKey pk = getAuthenticationKey(signKey); if (pk == null) throw new InvalidKeyException(signKey.toString()); candidatePks = new ArrayList(1); candidatePks.add(pk); } // Lookup the authorization key id in the target doc DIDURL realSignKey = null; DIDURL targetSignKey = null; lookup: for (PublicKey candidatePk : candidatePks) { for (PublicKey pk : targetDoc.getAuthorizationKeys()) { if (!pk.getController().equals(getSubject())) continue; if (pk.getPublicKeyBase58().equals(candidatePk.getPublicKeyBase58())) { realSignKey = candidatePk.getId(); targetSignKey = pk.getId(); break lookup; } } } if (realSignKey == null || targetSignKey == null) throw new InvalidKeyException("No matched authorization key."); DIDBackend.getInstance().deactivateDid(targetDoc, targetSignKey, this, realSignKey, storepass, adapter); } else { if (!targetDoc.hasController(getSubject())) throw new NotControllerException(getSubject().toString()); if (signKey == null) { signKey = getDefaultPublicKeyId(); } else { if (!signKey.equals(getDefaultPublicKeyId())) throw new InvalidKeyException(signKey.toString()); } DIDBackend.getInstance().deactivateDid(targetDoc, signKey, storepass, adapter); } } /** * Deactivate the target DID with the authorization. * * @param target the DID to be deactivated * @param signKey the key to sign the transaction, should be * authorized by the target DID * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void deactivate(DID target, DIDURL signKey, String storepass) throws DIDStoreException, DIDBackendException { deactivate(target, signKey, storepass, null); } /** * Deactivate the target DID with the authorization. * * @param target the DID to be deactivated * @param signKey the key to sign the transaction, should be * authorized by the target DID * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void deactivate(String target, String signKey, String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { deactivate(DID.valueOf(target), canonicalId(signKey), storepass, adapter); } /** * Deactivate the target DID with the authorization. * * @param target the DID to be deactivated * @param signKey the key to sign the transaction, should be * authorized by the target DID * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void deactivate(String target, String signKey, String storepass) throws DIDStoreException, DIDBackendException { deactivate(DID.valueOf(target), canonicalId(signKey), storepass, null); } /** * Deactivate the target DID with the authorization. This method will * check the authorized key automatically. * * @param target the DID to be deactivated * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void deactivate(DID target, String storepass, DIDTransactionAdapter adapter) throws DIDStoreException, DIDBackendException { deactivate(target, null, storepass, adapter); } /** * Deactivate the target DID with the authorization. This method will * check the authorized key automatically. * * @param target the DID to be deactivated * @param storepass the password for the DIDStore * @throws DIDStoreException if an error occurred when accessing the store * @throws DIDBackendException if an error occurred when publishing the transaction */ public void deactivate(DID target, String storepass) throws DIDStoreException, DIDBackendException { deactivate(target, null, storepass, null); } /** * Deactivate the target DID with the authorization in asynchronous mode. * * @param target the DID to be deactivated * @param signKey the key to sign the transaction, should be * authorized by the target DID * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return the new CompletableStage */ public CompletableFuture deactivateAsync(DID target, DIDURL signKey, String storepass, DIDTransactionAdapter adapter) { CompletableFuture future = CompletableFuture.runAsync(() -> { try { deactivate(target, signKey, storepass, adapter); } catch (DIDException e) { throw new CompletionException(e); } }); return future; } /** * Deactivate the target DID with the authorization in asynchronous mode. * * @param target the DID to be deactivated * @param signKey the key to sign the transaction, should be * authorized by the target DID * @param storepass the password for the DIDStore * @return the new CompletableStage */ public CompletableFuture deactivateAsync(DID target, DIDURL signKey, String storepass) { return deactivateAsync(target, signKey, storepass, null); } /** * Deactivate the target DID with the authorization in asynchronous mode. * * @param target the DID to be deactivated * @param signKey the key to sign the transaction, should be * authorized by the target DID * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return the new CompletableStage */ public CompletableFuture deactivateAsync(String target, String signKey, String storepass, DIDTransactionAdapter adapter) { CompletableFuture future = CompletableFuture.runAsync(() -> { try { deactivate(target, signKey, storepass, adapter); } catch (DIDException e) { throw new CompletionException(e); } }); return future; } /** * Deactivate the target DID with the authorization in asynchronous mode. * * @param target the DID to be deactivated * @param signKey the key to sign the transaction, should be * authorized by the target DID * @param storepass the password for the DIDStore * @return the new CompletableStage */ public CompletableFuture deactivateAsync(String target, String signKey, String storepass) { return deactivateAsync(target, signKey, storepass, null); } /** * Deactivate the target DID with the authorization in asynchronous mode. * This method will check the authorized key automatically. * * @param target the DID to be deactivated * @param storepass the password for the DIDStore * @param adapter an optional DIDTransactionAdapter, if null the method will * use the default adapter from the DIDBackend * @return the new CompletableStage */ public CompletableFuture deactivateAsync(DID target, String storepass, DIDTransactionAdapter adapter) { return deactivateAsync(target, null, storepass, adapter); } /** * Deactivate the target DID with the authorization in asynchronous mode. * This method will check the authorized key automatically. * * @param target the DID to be deactivated * @param storepass the password for the DIDStore * @return the new CompletableStage */ public CompletableFuture deactivateAsync(DID target, String storepass) { return deactivateAsync(target, null, storepass, null); } /** * Parse the DIDDocument object from a string JSON representation. * * @param content the string JSON content to deserialize the document object * @return the DIDDocument object * @throws MalformedDocumentException if a parse error occurs */ public static DIDDocument parse(String content) throws MalformedDocumentException { try { return parse(content, DIDDocument.class); } catch (DIDSyntaxException e) { if (e instanceof MalformedDocumentException) throw (MalformedDocumentException)e; else throw new MalformedDocumentException(e); } } /** * Parse the DIDDocument object from a Reader object. * * @param src the Reader object to deserialize the document object * @return the DIDDocument object * @throws MalformedDocumentException if a parse error occurs * @throws IOException if an IO error occurs */ public static DIDDocument parse(Reader src) throws MalformedDocumentException, IOException { try { return parse(src, DIDDocument.class); } catch (DIDSyntaxException e) { if (e instanceof MalformedDocumentException) throw (MalformedDocumentException)e; else throw new MalformedDocumentException(e); } } /** * Parse the DIDDocument object from an InputStream object. * * @param src the input stream object to deserialize the document object * @return the DIDDocument object * @throws MalformedDocumentException if a parse error occurs * @throws IOException if an IO error occurs */ public static DIDDocument parse(InputStream src) throws MalformedDocumentException, IOException { try { return parse(src, DIDDocument.class); } catch (DIDSyntaxException e) { if (e instanceof MalformedDocumentException) throw (MalformedDocumentException)e; else throw new MalformedDocumentException(e); } } /** * Parse the DIDDocument object from a File object. * * @param src the File object to deserialize the document object * @return the DIDDocument object * @throws MalformedDocumentException if a parse error occurs * @throws IOException if an IO error occurs */ public static DIDDocument parse(File src) throws MalformedDocumentException, IOException { try { return parse(src, DIDDocument.class); } catch (DIDSyntaxException e) { if (e instanceof MalformedDocumentException) throw (MalformedDocumentException)e; else throw new MalformedDocumentException(e); } } /** * Parse the DIDDocument object from a string JSON representation. * * @param content the string JSON content to deserialize the document object * @return the DIDDocument object * @throws MalformedDocumentException if a parse error occurs * @deprecated use {@link #parse(String)} instead */ @Deprecated public static DIDDocument fromJson(String content) throws MalformedDocumentException { return parse(content); } /** * Parse the DIDDocument object from a Reader object. * * @param src the Reader object to deserialize the document object * @return the DIDDocument object * @throws MalformedDocumentException if a parse error occurs * @throws IOException if an IO error occurs * @deprecated use {@link #parse(Reader)} instead */ @Deprecated public static DIDDocument fromJson(Reader src) throws MalformedDocumentException, IOException { return parse(src); } /** * Parse the DIDDocument object from an InputStream object. * * @param src the input stream object to deserialize the document object * @return the DIDDocument object * @throws MalformedDocumentException if a parse error occurs * @throws IOException if an IO error occurs * @deprecated use {@link #parse(InputStream)} instead */ @Deprecated public static DIDDocument fromJson(InputStream src) throws MalformedDocumentException, IOException { return parse(src); } /** * Parse the DIDDocument object from a File object. * * @param src the File object to deserialize the document object * @return the DIDDocument object * @throws MalformedDocumentException if a parse error occurs * @throws IOException if an IO error occurs * @deprecated use {@link #parse(File)} instead */ @Deprecated public static DIDDocument fromJson(File src) throws MalformedDocumentException, IOException { return parse(src); } /** * Builder object to create or modify the DIDDocument. */ public static class Builder { private DIDDocument document; private DIDDocument controllerDoc; /** * Constructs DID Document Builder with given DID and DIDStore. * * @param did the specified DID * @param store the DIDStore object */ protected Builder(DID did, DIDStore store) { this.document = new DIDDocument(did); if (Features.isEnabledJsonLdContext()) addDefaultContexts(); DIDMetadata metadata = new DIDMetadata(did, store); this.document.setMetadata(metadata); } /** * Constructs DID Document Builder with given customizedDid and DIDStore. * * @param did the specified DID * @param controller the effective controller to create the document * @param store the DIDStore object */ protected Builder(DID did, DIDDocument controller, DIDStore store) { this.document = new DIDDocument(did); if (Features.isEnabledJsonLdContext()) addDefaultContexts(); this.document.controllers = new ArrayList(); this.document.controllerDocs = new HashMap(); this.document.controllers.add(controller.getSubject()); this.document.controllerDocs.put(controller.getSubject(), controller); this.document.effectiveController = controller.getSubject(); this.document.setMetadata(new DIDMetadata(did, store)); this.controllerDoc = controller; } /** * Constructs DID Document Builder with given DID Document. * * @param doc a DIDDocument object to be modify */ protected Builder(DIDDocument doc) { this.document = doc.copy(); } /** * Construct a builder instance for the customized DID document * with effective controller. * * @param doc a customized DIDDocument object to be modify * @param controller one the the controller of the doc */ protected Builder(DIDDocument doc, DIDDocument controller) { this.document = doc.copy(); this.document.effectiveController = controller.getSubject(); // if (controller.getMetadata().attachedStore()) // this.document.getMetadata().setStore(controller.getMetadata().getStore()); this.controllerDoc = controller; } private DIDURL canonicalId(String id) { return DIDURL.valueOf(getSubject(), id); } private DIDURL canonicalId(DIDURL id) { if (id == null || id.getDid() != null) return id; return new DIDURL(getSubject(), id); } private void invalidateProof() { if (document.proofs != null && !document.proofs.isEmpty()) document.proofs.clear(); } private void checkNotSealed() throws AlreadySealedException { if (document == null) throw new AlreadySealedException(); } private void checkIsCustomized() throws NotCustomizedDIDException { if (!document.isCustomizedDid()) throw new NotCustomizedDIDException(document.getSubject().toString()); } /** * Add the default DID contexts(include W3C and Elastos DID contexts). * * @return the Builder instance for method chaining */ public Builder addDefaultContexts() { if (Features.isEnabledJsonLdContext()) { if (document.context == null) document.context = new ArrayList(); if (!document.context.contains(W3C_DID_CONTEXT)) document.context.add(W3C_DID_CONTEXT); if (!document.context.contains(ELASTOS_DID_CONTEXT)) document.context.add(ELASTOS_DID_CONTEXT); if (!document.context.contains(W3ID_SECURITY_CONTEXT)) document.context.add(W3ID_SECURITY_CONTEXT); } else { log.warn("JSON-LD context support not enabled"); } return this; } /** * Add a new context to the document. * * @param uri URI for the new context * @return the Builder instance for method chaining */ public Builder addContext(String uri) { if (Features.isEnabledJsonLdContext()) { if (document.context == null) document.context = new ArrayList(); if (!document.context.contains(uri)) document.context.add(uri); } else { log.warn("JSON-LD context support not enabled, the context {} will be ignored", uri); } return this; } /** * Add a new context to the document. * * @param uri URI for the new context * @return the Builder instance for method chaining */ public Builder addContext(URI uri) { return addContext(uri.toString()); } /** * Get the subject DID of this builder. * * @return the subject of document builder */ public DID getSubject() { checkNotSealed(); return document.getSubject(); } /** * Add a new controller to the customized DID document. * * @param controller the new controller's DID * @return the Builder instance for method chaining * @throws DIDResolveException if failed resolve the new controller's DID */ public Builder addController(DID controller) throws DIDResolveException { checkArgument(controller != null, "Invalid controller"); checkNotSealed(); checkIsCustomized(); checkArgument(!document.controllers.contains(controller), "Controller already exists"); DIDDocument controllerDoc = controller.resolve(true); if (controllerDoc == null) throw new DIDNotFoundException(controller.toString()); if (controllerDoc.isDeactivated()) throw new DIDDeactivatedException(controller.toString()); if (controllerDoc.isExpired()) throw new DIDExpiredException(controller.toString()); if (!controllerDoc.isGenuine()) throw new DIDNotGenuineException(controller.toString()); if (controllerDoc.isCustomizedDid()) throw new NotPrimitiveDIDException(controller.toString()); document.controllers.add(controller); document.controllerDocs.put(controller, controllerDoc); document.multisig = null; // invalidate multisig invalidateProof(); return this; } /** * Add a new controller to the customized DID document. * * @param controller the new controller's DID * @return the Builder instance for method chaining * @throws DIDResolveException if failed resolve the new controller's DID */ public Builder addController(String controller) throws DIDResolveException { return addController(DID.valueOf(controller)); } /** * Remove the specific controller from the customized document. * * @param controller the controller's DID to be remove * @return the Builder instance for method chaining */ public Builder removeController(DID controller) { checkArgument(controller != null, "Invalid controller"); checkNotSealed(); checkIsCustomized(); // checkArgument(document.controllers.contains(controller), "Controller not exists"); if (controller.equals(controllerDoc.getSubject())) throw new CanNotRemoveEffectiveControllerException(controller.toString()); if (document.controllers.remove(controller)) { document.controllerDocs.remove(controller); invalidateProof(); } return this; } /** * Remove the specific controller from the customized document. * * @param controller the controller's DID to be remove * @return the Builder instance for method chaining */ public Builder removeController(String controller) { return removeController(DID.valueOf(controller)); } /** * Set multisig specification for multi-controllers DID document. * * @param m the required signature count * @return the Builder instance for method chaining */ public Builder setMultiSignature(int m) { checkNotSealed(); checkIsCustomized(); checkArgument(m >= 1, "Invalid signature count"); int n = document.controllers.size(); checkArgument(m <= n, "Signature count exceeds the upper limit"); MultiSignature multisig = null; if (n > 1) multisig = new MultiSignature(m, n); if (document.multisig == null && multisig == null) return this; if (document.multisig != null && multisig != null && document.multisig.equals(multisig)) return this; document.multisig = new MultiSignature(m, n); invalidateProof(); return this; } private void addPublicKey(PublicKey key) { if (document.publicKeys == null) { document.publicKeys = new TreeMap(); document.authenticationKeys = new TreeMap(); document.authorizationKeys = new TreeMap(); } else { // Check the existence, both id and keyBase58 for (PublicKey pk : document.publicKeys.values()) { if (pk.getId().equals(key.getId())) throw new DIDObjectAlreadyExistException("PublicKey id '" + key.getId() + "' already exist."); if (pk.getPublicKeyBase58().equals(key.getPublicKeyBase58())) throw new DIDObjectAlreadyExistException("PublicKey '" + key.getPublicKeyBase58() + "' already exist."); } } document.publicKeys.put(key.getId(), key); if (document.defaultPublicKey == null) { String address = HDKey.toAddress(key.getPublicKeyBytes()); if (address.equals(getSubject().getMethodSpecificId())) { document.defaultPublicKey = key; document.authenticationKeys.put(key.getId(), key); } } invalidateProof(); } /** * Add PublicKey to did document builder. * * @param id the key id * @param type the key type * @param controller the owner of public key * @param pk the base58 encoded public key * @return the Builder instance for method chaining */ public Builder addPublicKey(DIDURL id, String type, DID controller, String pk) { checkNotSealed(); checkArgument(id != null && (id.getDid() == null || id.getDid().equals(getSubject())), "Invalid publicKey id"); checkArgument(pk != null && !pk.isEmpty(), "Invalid publicKey"); if (controller == null) controller = getSubject(); addPublicKey(new PublicKey(canonicalId(id), type, controller, pk)); return this; } /** * Add PublicKey to did document builder. * * @param id the key id * @param type the key type * @param controller the owner of public key * @param pk the base58 encoded public key * @return the Builder instance for method chaining */ public Builder addPublicKey(String id, String type, String controller, String pk) { return addPublicKey(canonicalId(id), type, DID.valueOf(controller), pk); } /** * Add PublicKey to did document builder. * * @param id the key id * @param controller the owner of public key * @param pk the base58 encoded public key * @return the Builder instance for method chaining */ public Builder addPublicKey(DIDURL id, DID controller, String pk) { return addPublicKey(id, null, controller, pk); } /** * Add PublicKey to did document builder. * * @param id the key id * @param controller the owner of public key * @param pk the base58 encoded public key * @return the Builder instance for method chaining */ public Builder addPublicKey(String id, String controller, String pk) { return addPublicKey(id, null, controller, pk); } /** * Add PublicKey to did document builder. * * @param id the key id * @param pk the base58 encoded public key * @return the Builder instance for method chaining */ public Builder addPublicKey(DIDURL id, String pk) { return addPublicKey(id, null, null, pk); } /** * Add PublicKey to did document builder. * * @param id the key id * @param pk the base58 encoded public key * @return the Builder instance for method chaining */ public Builder addPublicKey(String id, String pk) { return addPublicKey(id, null, null, pk); } /** * Remove the specific public key from the document. * If call this method with forced mode, it will remove the key * even it reference by the others. * * @param id the key id to be remove * @param force if true will remove the key even it reference by the others * @return the Builder instance for method chaining */ public Builder removePublicKey(DIDURL id, boolean force) { checkNotSealed(); checkArgument(id != null, "Invalid publicKey id"); if (document.publicKeys == null || document.publicKeys.isEmpty()) throw new DIDObjectNotExistException(id.toString()); id = canonicalId(id); PublicKey pk = document.publicKeys.get(id); if (pk == null) throw new DIDObjectNotExistException(id.toString()); // Can not remove default public key if (document.defaultPublicKey != null && document.defaultPublicKey.getId().equals(id)) throw new DIDObjectHasReferenceException(id.toString() + "is default key"); if (!force) { if (document.authenticationKeys.containsKey(pk.getId()) || document.authorizationKeys.containsKey(pk.getId())) throw new DIDObjectHasReferenceException(id.toString()); } if (document.publicKeys.remove(id) != null) { document.authenticationKeys.remove(id); document.authorizationKeys.remove(id); try { // TODO: should delete the loosed private key when store the document if (document.getMetadata().attachedStore()) document.getMetadata().getStore().deletePrivateKey(id); } catch (DIDStoreException ignore) { log.error("INTERNAL - Remove private key", ignore); } invalidateProof(); } return this; } /** * Remove the specific public key from the document. * If call this method with forced mode, it will remove the key * even it reference by the others. * * @param id the key id to be remove * @param force if true will remove the key even it reference by the others * @return the Builder instance for method chaining */ public Builder removePublicKey(String id, boolean force) { return removePublicKey(canonicalId(id), force); } /** * Remove the specific public key from the document. * It should not reference by authentication or authorization when * removing the public key. * * @param id the key id to be remove * @return the Builder instance for method chaining */ public Builder removePublicKey(DIDURL id) { return removePublicKey(id, false); } /** * Remove the specific public key from the document. * It should not reference by authentication or authorization when * removing the public key. * * @param id the key id to be remove * @return the Builder instance for method chaining */ public Builder removePublicKey(String id) { return removePublicKey(id, false); } /** * Add an exists public key as authentication key. * * @param id the key id to be add to authentication keys * @return the Builder instance for method chaining */ public Builder addAuthenticationKey(DIDURL id) { checkNotSealed(); checkArgument(id != null, "Invalid publicKey id"); if (document.publicKeys == null || document.publicKeys.isEmpty()) throw new DIDObjectNotExistException(id.toString()); id = canonicalId(id); PublicKey key = document.publicKeys.get(id); if (key == null) throw new DIDObjectNotExistException(id.toString()); // Check the controller is current DID subject if (!key.getController().equals(getSubject())) throw new IllegalUsageException(id.toString()); if (!document.authenticationKeys.containsKey(id)) { document.authenticationKeys.put(key.getId(), key); invalidateProof(); } return this; } /** * Add an exists public key as authentication key. * * @param id the key id to be add to authentication keys * @return the Builder instance for method chaining */ public Builder addAuthenticationKey(String id) { return addAuthenticationKey(canonicalId(id)); } /** * Add a new authentication key to the document. * * @param id the new authentication key id * @param pk the base58 encoded public key * @return the Builder instance for method chaining */ public Builder addAuthenticationKey(DIDURL id, String pk) { checkNotSealed(); checkArgument(id != null && (id.getDid() == null || id.getDid().equals(getSubject())), "Invalid publicKey id"); checkArgument(pk != null && !pk.isEmpty(), "Invalid publicKey"); PublicKey key = new PublicKey(canonicalId(id), null, getSubject(), pk); addPublicKey(key); document.authenticationKeys.put(key.getId(), key); return this; } /** * Add a new authentication key to the document. * * @param id the new authentication key id * @param pk the base58 encoded public key * @return the Builder instance for method chaining */ public Builder addAuthenticationKey(String id, String pk) { return addAuthenticationKey(canonicalId(id), pk); } /** * Remove the specific authentication key from the document. * After remove the authentication key, the key still in the * public keys, this method only remove the authentication * reference from the public key. * * @param id the key id to be remove * @return the Builder instance for method chaining */ public Builder removeAuthenticationKey(DIDURL id) { checkNotSealed(); checkArgument(id != null, "Invalid publicKey id"); if (document.publicKeys == null || document.publicKeys.isEmpty()) throw new DIDObjectNotExistException(id.toString()); id = canonicalId(id); PublicKey key = document.publicKeys.get(id); if (key == null || !document.authenticationKeys.containsKey(id)) throw new DIDObjectNotExistException(id.toString()); // Can not remove default public key if (document.defaultPublicKey != null && document.defaultPublicKey.getId().equals(id)) throw new DIDObjectHasReferenceException( "Cannot remove the default PublicKey from authentication."); if (document.authenticationKeys.remove(id) != null) invalidateProof(); else throw new DIDObjectNotExistException(id.toString()); return this; } /** * Remove the specific authentication key from the document. * After remove the authentication key, the key still in the * public keys, this method only remove the authentication * reference from the public key. * * @param id the key id to be remove * @return the Builder instance for method chaining */ public Builder removeAuthenticationKey(String id) { return removeAuthenticationKey(canonicalId(id)); } /** * Add an exists public key as authorization key. * * @param id the key id to be add to authorization keys * @return the Builder instance for method chaining */ public Builder addAuthorizationKey(DIDURL id) { checkNotSealed(); checkArgument(id != null, "Invalid publicKey id"); if (document.isCustomizedDid()) throw new NotPrimitiveDIDException(getSubject().toString()); if (document.publicKeys == null || document.publicKeys.isEmpty()) throw new DIDObjectNotExistException(id.toString()); id = canonicalId(id); PublicKey key = document.publicKeys.get(id); if (key == null) throw new DIDObjectNotExistException(id.toString()); // Can not authorize to self if (key.getController().equals(getSubject())) throw new IllegalUsageException(id.toString()); if (!document.authorizationKeys.containsKey(id)) { document.authorizationKeys.put(key.getId(), key); invalidateProof(); } return this; } /** * Add an exists public key as authorization key. * * @param id the key id to be add to authorization keys * @return the Builder instance for method chaining */ public Builder addAuthorizationKey(String id) { return addAuthorizationKey(canonicalId(id)); } /** * Add a new authorization key. * * @param id the new authorization key id * @param controller the owner of the authorization * @param pk the base58 encoded public key * @return the Builder instance for method chaining */ public Builder addAuthorizationKey(DIDURL id, DID controller, String pk) { checkNotSealed(); checkArgument(id != null && (id.getDid() == null || id.getDid().equals(getSubject())), "Invalid publicKey id"); checkArgument(pk != null && !pk.isEmpty(), "Invalid publicKey"); if (document.isCustomizedDid()) throw new NotPrimitiveDIDException(getSubject().toString()); // Can not authorize to self if (controller.equals(getSubject())) throw new IllegalUsageException("Key's controller is self."); PublicKey key = new PublicKey(canonicalId(id), null, controller, pk); addPublicKey(key); document.authorizationKeys.put(key.getId(), key); return this; } /** * Add a new authorization key. * * @param id the new authorization key id * @param controller the owner of the authorization * @param pk the base58 encoded public key * @return the Builder instance for method chaining */ public Builder addAuthorizationKey(String id, String controller, String pk) { return addAuthorizationKey(canonicalId(id), DID.valueOf(controller), pk); } /** * Authorize to the DID and the specific key. * * @param id the new authorization key id * @param controller the DID that authorized to * @param key the key id that controller by the controller to be authorize * @return the Builder instance for method chaining * @throws DIDResolveException if an error occurred when resolving the DIDs */ public Builder authorizeDid(DIDURL id, DID controller, DIDURL key) throws DIDResolveException { checkNotSealed(); checkArgument(id != null && (id.getDid() == null || id.getDid().equals(getSubject())), "Invalid publicKey id"); checkArgument(controller != null && !controller.equals(getSubject()), "Invalid controller"); if (document.isCustomizedDid()) throw new NotPrimitiveDIDException(getSubject().toString()); DIDDocument controllerDoc = controller.resolve(); if (controllerDoc == null) throw new DIDNotFoundException(id.toString()); if (controllerDoc.isDeactivated()) throw new DIDDeactivatedException(controller.toString()); if (controllerDoc.isExpired()) throw new DIDExpiredException(controller.toString()); if (!controllerDoc.isGenuine()) throw new DIDNotGenuineException(controller.toString()); if (controllerDoc.isCustomizedDid()) throw new NotPrimitiveDIDException(controller.toString()); if (key == null) key = controllerDoc.getDefaultPublicKeyId(); // Check the key should be a authentication key. PublicKey targetPk = controllerDoc.getAuthenticationKey(key); if (targetPk == null) throw new DIDObjectNotExistException(key.toString()); PublicKey pk = new PublicKey(canonicalId(id), targetPk.getType(), controller, targetPk.getPublicKeyBase58()); addPublicKey(pk); document.authorizationKeys.put(pk.getId(), pk); return this; } /** * Authorize to the DID and the DID's default key. * * @param id the new authorization key id * @param controller the DID that authorized to * @return the Builder instance for method chaining * @throws DIDResolveException if an error occurred when resolving the DIDs */ public Builder authorizeDid(DIDURL id, DID controller) throws DIDResolveException { return authorizeDid(id, controller, null); } /** * Authorize to the DID and the specific key. * * @param id the new authorization key id * @param controller the DID that authorized to * @param key the key id that controller by the controller to be authorize * @return the Builder instance for method chaining * @throws DIDResolveException if an error occurred when resolving the DIDs */ public Builder authorizeDid(String id, String controller, String key) throws DIDResolveException { return authorizeDid(canonicalId(id), DID.valueOf(controller), DIDURL.valueOf(controller, key)); } /** * Authorize to the DID and the DID's default key. * * @param id the new authorization key id * @param controller the DID that authorized to * @return the Builder instance for method chaining * @throws DIDResolveException if an error occurred when resolving the DIDs */ public Builder authorizeDid(String id, String controller) throws DIDResolveException { return authorizeDid(id, controller, null); } /** * Remove the specific authorization key from the document. * After remove the authorization key, the key still in the * public keys, this method only remove the authorization * reference from the public key. * * @param id the key id to be remove * @return the Builder instance for method chaining */ public Builder removeAuthorizationKey(DIDURL id) { checkNotSealed(); checkArgument(id != null, "Invalid publicKey id"); if (document.publicKeys == null || document.publicKeys.isEmpty()) throw new DIDObjectNotExistException(id.toString()); id = canonicalId(id); PublicKey key = document.publicKeys.get(id); if (key == null) throw new DIDObjectNotExistException(id.toString()); if (document.authorizationKeys.remove(id) != null) invalidateProof(); else throw new DIDObjectNotExistException(id.toString()); return this; } /** * Remove the specific authorization key from the document. * After remove the authorization key, the key still in the * public keys, this method only remove the authorization * reference from the public key. * * @param id the key id to be remove * @return the Builder instance for method chaining */ public Builder removeAuthorizationKey(String id) { return removeAuthorizationKey(canonicalId(id)); } /** * Add a credential object to the document. * * @param vc the VerifiableCredential object to be add * @return the Builder instance for method chaining * @throws DIDResolveException if an error occurred when resolving DID */ public Builder addCredential(VerifiableCredential vc) throws DIDResolveException { checkNotSealed(); checkArgument(vc != null, "Invalid credential"); // Check the credential belongs to current DID. if (!vc.getSubject().getId().equals(getSubject())) throw new IllegalUsageException(vc.getSubject().getId().toString()); // The credential should be genuine boolean genuine = vc.isSelfProclaimed() ? vc.isGenuineInternal(document) : vc.isGenuine(); if (!genuine) throw new IllegalArgumentException(new MalformedCredentialException(vc.getId().toString())); return addCredentialUncheck(vc); } private Builder addCredentialUncheck(VerifiableCredential vc) { if (document.credentials == null) { document.credentials = new TreeMap(); } else { if (document.credentials.containsKey(vc.getId())) throw new DIDObjectAlreadyExistException(vc.getId().toString()); } document.credentials.put(vc.getId(), vc); invalidateProof(); return this; } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param types the credential types * @param subject the claims for the credential subject * @param expirationDate the expiration time for the credential * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(DIDURL id, String[] types, Map subject, Date expirationDate, String storepass) throws DIDStoreException { checkNotSealed(); checkArgument(id != null && (id.getDid() == null || id.getDid().equals(getSubject())), "Invalid publicKey id"); checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); Issuer issuer = new Issuer(document); VerifiableCredential.Builder cb = issuer.issueFor(document.getSubject()); if (expirationDate == null) expirationDate = document.getExpires(); try { VerifiableCredential vc = cb.id(canonicalId(id)) .types(types) .properties(subject) .expirationDate(expirationDate) .seal(storepass); return addCredentialUncheck(vc); } catch (MalformedCredentialException ignore) { throw new UnknownInternalException(ignore); } } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param types the credential types * @param subject the claims for the credential subject * @param expirationDate the expiration time for the credential * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(String id, String[] types, Map subject, Date expirationDate, String storepass) throws DIDStoreException { return addCredential(canonicalId(id), types, subject, expirationDate, storepass); } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param subject the claims for the credential subject * @param expirationDate the expiration time for the credential * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(DIDURL id, Map subject, Date expirationDate, String storepass) throws DIDStoreException { return addCredential(id, null, subject, expirationDate, storepass); } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param subject the claims for the credential subject * @param expirationDate the expiration time for the credential * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(String id, Map subject, Date expirationDate, String storepass) throws DIDStoreException { return addCredential(canonicalId(id), null, subject, expirationDate, storepass); } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param types the credential types * @param subject the claims for the credential subject * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(DIDURL id, String[] types, Map subject, String storepass) throws DIDStoreException { return addCredential(id, types, subject, null, storepass); } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param types the credential types * @param subject the claims for the credential subject * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(String id, String[] types, Map subject, String storepass) throws DIDStoreException { return addCredential(canonicalId(id), types, subject, null, storepass); } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param subject the claims for the credential subject * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(DIDURL id, Map subject, String storepass) throws DIDStoreException { return addCredential(id, null, subject, null, storepass); } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param subject the claims for the credential subject * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(String id, Map subject, String storepass) throws DIDStoreException { return addCredential(canonicalId(id), null, subject, null, storepass); } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param types the credential types * @param jsonSubject a JSON string represented claims for the credential subject * @param expirationDate the expiration time for the credential * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(DIDURL id, String[] types, String jsonSubject, Date expirationDate, String storepass) throws DIDStoreException { checkNotSealed(); checkArgument(id != null && (id.getDid() == null || id.getDid().equals(getSubject())), "Invalid publicKey id"); checkArgument(jsonSubject != null && !jsonSubject.isEmpty(), "Invalid json"); checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); Issuer issuer = new Issuer(document); VerifiableCredential.Builder cb = issuer.issueFor(document.getSubject()); if (expirationDate == null) expirationDate = document.expires; try { VerifiableCredential vc = cb.id(canonicalId(id)) .types(types) .properties(jsonSubject) .expirationDate(expirationDate) .seal(storepass); return addCredentialUncheck(vc); } catch (MalformedCredentialException ignore) { throw new UnknownInternalException(ignore); } } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param types the credential types * @param jsonSubject a JSON string represented claims for the credential subject * @param expirationDate the expiration time for the credential * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(String id, String[] types, String jsonSubject, Date expirationDate, String storepass) throws DIDStoreException { return addCredential(canonicalId(id), types, jsonSubject, expirationDate, storepass); } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param jsonSubject a JSON string represented claims for the credential subject * @param expirationDate the expiration time for the credential * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(DIDURL id, String jsonSubject, Date expirationDate, String storepass) throws DIDStoreException { return addCredential(id, null, jsonSubject, expirationDate, storepass); } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param jsonSubject a JSON string represented claims for the credential subject * @param expirationDate the expiration time for the credential * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(String id, String jsonSubject, Date expirationDate, String storepass) throws DIDStoreException { return addCredential(canonicalId(id), null, jsonSubject, expirationDate, storepass); } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param types the credential types * @param jsonSubject a JSON string represented claims for the credential subject * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(DIDURL id, String[] types, String jsonSubject, String storepass) throws DIDStoreException { return addCredential(id, types, jsonSubject, null, storepass); } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param types the credential types * @param jsonSubject a JSON string represented claims for the credential subject * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(String id, String[] types, String jsonSubject, String storepass) throws DIDStoreException { return addCredential(canonicalId(id), types, jsonSubject, null, storepass); } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param jsonSubject a JSON string represented claims for the credential subject * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(DIDURL id, String jsonSubject, String storepass) throws DIDStoreException { return addCredential(id, null, jsonSubject, null, storepass); } /** * Add a self-proclaimed credential to the document. * * @param id the credential id * @param jsonSubject a JSON string represented claims for the credential subject * @param storepass the password for the store * @return the Builder instance for method chaining * @throws DIDStoreException if an error occurred when accessing the store */ public Builder addCredential(String id, String jsonSubject, String storepass) throws DIDStoreException { return addCredential(canonicalId(id), null, jsonSubject, null, storepass); } /** * Remove the specific credential from the document. * * @param id the id of the credential to be remove * @return the Builder instance for method chaining */ public Builder removeCredential(DIDURL id) { checkNotSealed(); checkArgument(id != null, "Invalid credential id"); if (document.credentials == null || document.credentials.isEmpty()) throw new DIDObjectNotExistException(id.toString()); if (document.credentials.remove(canonicalId(id)) != null) invalidateProof(); else throw new DIDObjectNotExistException(id.toString()); return this; } /** * Remove the specific credential from the document. * * @param id the id of the credential to be remove * @return the Builder instance for method chaining */ public Builder removeCredential(String id) { return removeCredential(canonicalId(id)); } /** * Add a new service to the document. * * @param id the service id * @param type the service type * @param endpoint the service endpoint * @param properties the application defined extra properties * @return the Builder instance for method chaining */ public Builder addService(DIDURL id, String type, String endpoint, Map properties) { checkNotSealed(); checkArgument(id != null && (id.getDid() == null || id.getDid().equals(getSubject())), "Invalid publicKey id"); checkArgument(type != null && !type.isEmpty(), "Invalid type"); checkArgument(endpoint != null && !endpoint.isEmpty(), "Invalid endpoint"); Service svc = new Service(canonicalId(id), type, endpoint, properties); if (document.services == null) document.services = new TreeMap(); else { if (document.services.containsKey(svc.getId())) throw new DIDObjectAlreadyExistException("Service '" + svc.getId() + "' already exist."); } document.services.put(svc.getId(), svc); invalidateProof(); return this; } /** * Add a new service to the document. * * @param id the service id * @param type the service type * @param endpoint the service endpoint * @param properties the application defined extra properties * @return the Builder instance for method chaining */ public Builder addService(String id, String type, String endpoint, Map properties) { return addService(canonicalId(id), type, endpoint, properties); } /** * Add a new service to the document. * * @param id the service id * @param type the service type * @param endpoint the service endpoint * @return the Builder instance for method chaining */ public Builder addService(DIDURL id, String type, String endpoint) { return addService(id, type, endpoint, null); } /** * Add a new service to the document. * * @param id the service id * @param type the service type * @param endpoint the service endpoint * @return the Builder instance for method chaining */ public Builder addService(String id, String type, String endpoint) { return addService(canonicalId(id), type, endpoint, null); } /** * Remove the specific service from the document. * * @param id the id of the service to be remove * @return the Builder instance for method chaining */ public Builder removeService(DIDURL id) { checkNotSealed(); checkArgument(id != null, "Invalid credential id"); if (document.services == null || document.services.isEmpty()) throw new DIDObjectNotExistException(id.toString()); if (document.services.remove(canonicalId(id)) != null) invalidateProof(); else throw new DIDObjectNotExistException(id.toString()); return this; } /** * Remove the specific service from the document. * * @param id the id of the service to be remove * @return the Builder instance for method chaining */ public Builder removeService(String id) { return removeService(canonicalId(id)); } private Calendar getMaxExpires() { Calendar cal = Calendar.getInstance(Constants.UTC); cal.add(Calendar.YEAR, Constants.MAX_VALID_YEARS); return cal; } /** * Set the document use the default expire time. * * @return the Builder instance for method chaining */ public Builder setDefaultExpires() { checkNotSealed(); document.expires = getMaxExpires().getTime(); invalidateProof(); return this; } /** * Set the expires time for the document. * * @param expires the expire time * @return the Builder instance for method chaining */ public Builder setExpires(Date expires) { checkNotSealed(); checkArgument(expires != null, "Invalid expires"); Calendar cal = Calendar.getInstance(Constants.UTC); cal.setTime(expires); if (cal.after(getMaxExpires())) throw new IllegalArgumentException("Invalid expires, out of range."); document.expires = expires; invalidateProof(); return this; } /** * Remove the proof that created by the specific controller. * * @param controller the controller's DID * @return the Builder instance for method chaining */ public Builder removeProof(DID controller) { checkNotSealed(); checkArgument(controller != null, "Invalid controller"); if (document.proofs == null || document.proofs.isEmpty()) return this; if (document.proofs.remove(controller) == null) throw new DIDObjectNotExistException("No proof signed by: " + controller); return this; } private void sanitize() throws MalformedDocumentException { if (document.isCustomizedDid()) { if (document.controllers == null || document.controllers.isEmpty()) throw new MalformedDocumentException("Missing controllers"); if (document.controllers.size() > 1) { if (document.multisig == null) throw new MalformedDocumentException("Missing multisig"); if (document.multisig.n() != document.controllers.size()) throw new MalformedDocumentException("Invalid multisig, not matched with controllers"); } else { if (document.multisig != null) throw new MalformedDocumentException("Invalid multisig"); } } int sigs = document.multisig == null ? 1 : document.multisig.m(); if (document.proofs != null && document.proofs.size() == sigs) throw new AlreadySealedException(getSubject().toString()); if (document.controllers == null || document.controllers.isEmpty()) { document.controllers = Collections.emptyList(); document.controllerDocs = Collections.emptyMap(); } else { Collections.sort(document.controllers); } if (document.publicKeys == null || document.publicKeys.isEmpty()) { document.publicKeys = Collections.emptyMap(); document.authenticationKeys = Collections.emptyMap(); document.authorizationKeys = Collections.emptyMap(); document._publickeys = Collections.emptyList(); document._authentications = Collections.emptyList(); document._authorizations = Collections.emptyList(); } else { document._publickeys = new ArrayList(document.publicKeys.values()); document._authentications = new ArrayList(); document._authorizations = new ArrayList(); if (document.authenticationKeys.isEmpty()) { document._authentications = Collections.emptyList(); document.authenticationKeys = Collections.emptyMap(); } else { for (PublicKey pk : document.authenticationKeys.values()) document._authentications.add(new PublicKeyReference(pk)); } if (document.authorizationKeys.isEmpty()) { document._authorizations = Collections.emptyList(); document.authorizationKeys = Collections.emptyMap(); } else { for (PublicKey pk : document.authorizationKeys.values()) document._authorizations.add(new PublicKeyReference(pk)); } } if (document.credentials == null || document.credentials.isEmpty()) { document.credentials = Collections.emptyMap(); document._credentials = Collections.emptyList(); } else { document._credentials = new ArrayList(document.credentials.values()); } if (document.services == null || document.services.isEmpty()) { document.services = Collections.emptyMap(); document._services = Collections.emptyList(); } else { document._services = new ArrayList(document.services.values()); } if (document.proofs == null || document.proofs.isEmpty()) { if (document.getExpires() == null) setDefaultExpires(); } if (document.proofs == null) document.proofs = new HashMap(); document._proofs = null; } /** * Seal the document object, attach the generated proof to the * document. * * @param storepass the password for the DIDStore * @return the new DIDDocument object * @throws InvalidKeyException if no valid sign key to seal the document * @throws MalformedDocumentException if the DIDDocument is malformed * @throws DIDStoreException if an error occurs when accessing the store */ public DIDDocument seal(String storepass) throws MalformedDocumentException, DIDStoreException { checkNotSealed(); checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); sanitize(); DIDDocument signerDoc = document.isCustomizedDid() ? controllerDoc : document; DIDURL signKey = signerDoc.getDefaultPublicKeyId(); if (document.proofs.containsKey(signerDoc.getSubject())) throw new AlreadySignedException(signerDoc.getSubject().toString()); String json = document.serialize(true); String sig = document.sign(signKey, storepass, json.getBytes()); Proof proof = new Proof(signKey, sig); document.proofs.put(proof.getCreator().getDid(), proof); document._proofs = new ArrayList(document.proofs.values()); Collections.sort(document._proofs); // Invalidate builder DIDDocument doc = document; this.document = null; return doc; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy