/// <summary> /// Copies values from signed profile information to properties of this instance of the identity. /// </summary> /// <param name="SignedProfile">Signed information about the profile.</param> /// <param name="HostingServerId">In case of NeighborhIdentity, this is set to network identifier of the hosting server.</param> public void CopyFromSignedProfileInformation(SignedProfileInformation SignedProfile, byte[] HostingServerId) { if (HostingServerId == null) { HostingServerId = new byte[0]; } ProfileInformation profile = SignedProfile.Profile; byte[] pubKey = profile.PublicKey.ToByteArray(); byte[] identityId = Crypto.Sha256(pubKey); GpsLocation location = new GpsLocation(profile.Latitude, profile.Longitude); this.IdentityId = identityId; this.HostingServerId = HostingServerId; this.PublicKey = pubKey; this.Version = profile.Version.ToByteArray(); this.Name = profile.Name; this.Type = profile.Type; this.InitialLocationLatitude = location.Latitude; this.InitialLocationLongitude = location.Longitude; this.ExtraData = profile.ExtraData; this.ProfileImage = profile.ProfileImageHash.Length != 0 ? profile.ProfileImageHash.ToByteArray() : null; this.ThumbnailImage = profile.ThumbnailImageHash.Length != 0 ? profile.ThumbnailImageHash.ToByteArray() : null; this.Signature = SignedProfile.Signature.ToByteArray(); }
/// <summary> /// Creates a new instance of identity from SignedProfileInformation data structure. /// </summary> /// <param name="SignedProfile">Signed information about the profile.</param> /// <param name="HostingServerId">In case of NeighborhIdentity, this is set to network identifier of the hosting server.</param> /// <returns>New identity instance.</returns> public static NeighborIdentity FromSignedProfileInformation(SignedProfileInformation SignedProfile, byte[] HostingServerId) { NeighborIdentity res = new NeighborIdentity(); res.CopyFromSignedProfileInformation(SignedProfile, HostingServerId); return(res); }
/// <summary> /// Checks whether a signed profile information is valid. /// </summary> /// <param name="SignedProfile">Signed profile information to check.</param> /// <param name="IdentityPublicKey">Public key of the profile's identity.</param> /// <param name="MessageBuilder">Client's network message builder.</param> /// <param name="RequestMessage">Full request message from client.</param> /// <param name="ErrorPrefix">Prefix to add to the validation error details.</param> /// <param name="InvalidSignatureToDetails">If set to true, invalid signature error will be reported as invalid value in signature field.</param> /// <param name="ErrorResponse">If the function fails, this is filled with error response message that is ready to be sent to the client.</param> /// <returns>true if the signed profile information is valid and signed correctly by the given identity, false otherwise.</returns> public static bool ValidateSignedProfileInformation(SignedProfileInformation SignedProfile, byte[] IdentityPublicKey, PsMessageBuilder MessageBuilder, PsProtocolMessage RequestMessage, string ErrorPrefix, bool InvalidSignatureToDetails, out PsProtocolMessage ErrorResponse) { log.Trace("()"); ErrorResponse = null; if (SignedProfile == null) { SignedProfile = new SignedProfileInformation(); } if (SignedProfile.Profile == null) { SignedProfile.Profile = new ProfileInformation(); } bool res = false; if (ValidateProfileInformation(SignedProfile.Profile, IdentityPublicKey, MessageBuilder, RequestMessage, ErrorPrefix + "profile.", out ErrorResponse)) { // IdentityBase.InternalInvalidProfileType is a special internal type of profile that we use to prevent problems in the network. // This is not an elegant solution. // See NeighborhoodActionProcessor.NeighborhoodProfileUpdateAsync case NeighborhoodActionType.AddProfile for more information. if (SignedProfile.Profile.Type != IdentityBase.InternalInvalidProfileType) { byte[] signature = SignedProfile.Signature.ToByteArray(); byte[] data = SignedProfile.Profile.ToByteArray(); if (Ed25519.Verify(signature, data, IdentityPublicKey)) { res = true; } else { ErrorResponse = InvalidSignatureToDetails ? MessageBuilder.CreateErrorInvalidValueResponse(RequestMessage, ErrorPrefix + "signature") : MessageBuilder.CreateErrorInvalidSignatureResponse(RequestMessage); } } } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Creates SignedProfileInformation representation of the identity's profile. /// </summary> /// <returns>SignedProfileInformation structure describing the profile.</returns> public SignedProfileInformation ToSignedProfileInformation() { GpsLocation location = this.GetInitialLocation(); SignedProfileInformation res = new SignedProfileInformation() { Profile = new ProfileInformation() { Version = new SemVer(this.Version).ToByteString(), PublicKey = ProtocolHelper.ByteArrayToByteString(this.PublicKey), Type = this.Type, Name = this.Name, ExtraData = this.ExtraData, Latitude = location.GetLocationTypeLatitude(), Longitude = location.GetLocationTypeLongitude(), ProfileImageHash = ProtocolHelper.ByteArrayToByteString(this.ProfileImage != null ? this.ProfileImage : new byte[0]), ThumbnailImageHash = ProtocolHelper.ByteArrayToByteString(this.ThumbnailImage != null ? this.ThumbnailImage: new byte[0]) }, Signature = ProtocolHelper.ByteArrayToByteString(this.Signature != null ? this.Signature : new byte[0]) }; return(res); }
/// <summary> /// Checks whether the update profile request is valid. /// </summary> /// <param name="Identity">Identity on which the update operation is about to be performed.</param> /// <param name="UpdateProfileRequest">Update profile request part of the client's request message.</param> /// <param name="MessageBuilder">Client's network message builder.</param> /// <param name="RequestMessage">Full request message from client.</param> /// <param name="ErrorResponse">If the function fails, this is filled with error response message that is ready to be sent to the client.</param> /// <returns>true if the profile update request can be applied, false otherwise.</returns> public static bool ValidateUpdateProfileRequest(HostedIdentity Identity, UpdateProfileRequest UpdateProfileRequest, PsMessageBuilder MessageBuilder, PsProtocolMessage RequestMessage, out PsProtocolMessage ErrorResponse) { log.Trace("(Identity.IdentityId:'{0}')", Identity.IdentityId.ToHex()); bool res = false; ErrorResponse = null; if (UpdateProfileRequest == null) { UpdateProfileRequest = new UpdateProfileRequest(); } if (UpdateProfileRequest.Profile == null) { UpdateProfileRequest.Profile = new ProfileInformation(); } SignedProfileInformation signedProfile = new SignedProfileInformation() { Profile = UpdateProfileRequest.Profile, Signature = RequestMessage.Request.ConversationRequest.Signature }; if (ValidateSignedProfileInformation(signedProfile, Identity.PublicKey, MessageBuilder, RequestMessage, "", false, out ErrorResponse)) { string details = null; // Check if the update is a valid profile initialization. // If the profile is updated for the first time (aka is being initialized), // NoPropagation must be false. if (!Identity.Initialized && UpdateProfileRequest.NoPropagation) { log.Debug("Attempt to initialize profile with NoPropagation set to false."); details = "noPropagation"; } if (details == null) { // Profile type is unchangable after registration. bool identityTypeValid = Identity.Type == UpdateProfileRequest.Profile.Type; if (!identityTypeValid) { log.Debug("Attempt to change profile type."); details = "profile.type"; } } if (details == null) { byte[] profileImage = UpdateProfileRequest.ProfileImage.ToByteArray(); byte[] profileImageHash = UpdateProfileRequest.Profile.ProfileImageHash.ToByteArray(); // Profile image must be PNG or JPEG image, no bigger than Identity.MaxProfileImageLengthBytes. bool eraseImage = profileImageHash.Length == 0; bool profileImageValid = (profileImage.Length <= HostedIdentity.MaxProfileImageLengthBytes) && (eraseImage || ImageManager.ValidateImageWithHash(profileImage, profileImageHash)); if (!profileImageValid) { log.Debug("Invalid profile image."); details = "profileImage"; } } if (details == null) { byte[] thumbnailImage = UpdateProfileRequest.ThumbnailImage.ToByteArray(); byte[] thumbnailImageHash = UpdateProfileRequest.Profile.ThumbnailImageHash.ToByteArray(); // Profile image must be PNG or JPEG image, no bigger than Identity.MaxThumbnailImageLengthBytes. bool eraseImage = thumbnailImageHash.Length == 0; bool thumbnailImageValid = (thumbnailImage.Length <= IdentityBase.MaxThumbnailImageLengthBytes) && (eraseImage || ImageManager.ValidateImageWithHash(thumbnailImage, thumbnailImageHash)); if (!thumbnailImageValid) { log.Debug("Invalid thumbnail image."); details = "thumbnailImage"; } } if (details == null) { res = true; } else { ErrorResponse = MessageBuilder.CreateErrorInvalidValueResponse(RequestMessage, details); } } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Updates identity profile and creates neighborhood action to propagate the change unless the client did not want the propagation. /// </summary> /// <param name="IdentityId">Network identifier of the identity to update.</param> /// <param name="SignedProfile">Signed profile information with updated values.</param> /// <param name="ProfileImageChanged">True if profile image is about to change.</param> /// <param name="ThumbnailImageChanged">True if thumbnail image is about to change.</param> /// <param name="NoPropagation">True if the client does not want this change of profile to be propagated to the neighborhood.</param> /// <param name="IdentityNotFound">If the function fails because the identity is not found, this referenced value is set to true.</param> /// <param name="ImagesToDelete">If the function succeeds and the profile images are altered, old image files has to be deleted, in which case their hashes /// are returned in this list, which has to be initialized by the caller.</param> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> UpdateProfileAndPropagateAsync(byte[] IdentityId, SignedProfileInformation SignedProfile, bool ProfileImageChanged, bool ThumbnailImageChanged, bool NoPropagation, StrongBox <bool> IdentityNotFound, List <byte[]> ImagesToDelete) { log.Trace("()"); bool res = false; bool signalNeighborhoodAction = false; bool success = false; List <byte[]> imagesToDelete = new List <byte[]>(); ProfileInformation profile = SignedProfile.Profile; DatabaseLock[] lockObjects = new DatabaseLock[] { UnitOfWork.HostedIdentityLock, UnitOfWork.FollowerLock, UnitOfWork.NeighborhoodActionLock }; using (IDbContextTransaction transaction = await unitOfWork.BeginTransactionWithLockAsync(lockObjects)) { try { HostedIdentity identity = (await GetAsync(i => (i.IdentityId == IdentityId) && (i.Cancelled == false))).FirstOrDefault(); if (identity != null) { bool isProfileInitialization = !identity.Initialized; identity.Initialized = true; identity.Version = profile.Version.ToByteArray(); identity.Name = profile.Name; GpsLocation location = new GpsLocation(profile.Latitude, profile.Longitude); identity.SetInitialLocation(location); identity.ExtraData = profile.ExtraData; identity.Signature = SignedProfile.Signature.ToByteArray(); if (ProfileImageChanged) { if (identity.ProfileImage != null) { imagesToDelete.Add(identity.ProfileImage); } identity.ProfileImage = profile.ProfileImageHash.Length != 0 ? profile.ProfileImageHash.ToByteArray() : null; } if (ThumbnailImageChanged) { if (identity.ThumbnailImage != null) { imagesToDelete.Add(identity.ThumbnailImage); } identity.ThumbnailImage = profile.ThumbnailImageHash.Length != 0 ? profile.ThumbnailImageHash.ToByteArray() : null; } Update(identity); if (!NoPropagation) { // The profile change has to be propagated to all our followers // we create database actions that will be processed by dedicated thread. NeighborhoodActionType actionType = isProfileInitialization ? NeighborhoodActionType.AddProfile : NeighborhoodActionType.ChangeProfile; string extraInfo = identity.PublicKey.ToHex(); signalNeighborhoodAction = await unitOfWork.NeighborhoodActionRepository.AddIdentityProfileFollowerActionsAsync(actionType, identity.IdentityId, extraInfo); } await unitOfWork.SaveThrowAsync(); transaction.Commit(); success = true; } else { IdentityNotFound.Value = true; } } catch (Exception e) { log.Error("Exception occurred: {0}", e.ToString()); } if (!success) { log.Warn("Rolling back transaction."); unitOfWork.SafeTransactionRollback(transaction); } unitOfWork.ReleaseLock(lockObjects); } if (success) { // Only when the function succeeds the old images can be deleted. ImagesToDelete.AddRange(imagesToDelete); // Send signal to neighborhood action processor to process the new series of actions. if (signalNeighborhoodAction) { Network.NeighborhoodActionProcessor neighborhoodActionProcessor = (Network.NeighborhoodActionProcessor)Base.ComponentDictionary[Network.NeighborhoodActionProcessor.ComponentName]; neighborhoodActionProcessor.Signal(); } res = true; } log.Trace("(-):{0}", res); return(res); }