/// <summary> /// Initializes a new instance of the <see cref="GNTPRequest"/> class. /// </summary> /// <param name="version">The version of the GNTP request.</param> /// <param name="directive">The type of GNTP request.</param> /// <param name="key">The key used to validate and encrypt the message.</param> /// <param name="headers">The collection of headers parsed from the current request.</param> /// <param name="applicationName">The name of the application sending the request.</param> /// <param name="notificationsToBeRegistered">A collection of the groups of headers for each notification type to be registered.</param> /// <param name="callbackContext">The callback context associated with the request.</param> public GNTPRequest(string version, RequestType directive, Key key, HeaderCollection headers, string applicationName, List<HeaderCollection> notificationsToBeRegistered, CallbackContext callbackContext) { this.version = version; this.directive = directive; this.key = key; this.headers = headers; this.applicationName = applicationName; this.notificationsToBeRegistered = notificationsToBeRegistered; this.callbackContext = callbackContext; }
private void ProcessBuffer() { try { Data data = new Data(buffer.ToArray()); string s = data.ToString(); alreadyReceived.Append(s); if (tag == START_TAG) { // start looking for GNTP header tag = GNTP_IDENTIFIER_TAG; ContinueProcessing(); } else if (tag == GNTP_IDENTIFIER_TAG) { string line = s; Match match = ParseGNTPHeaderLine(line, this.passwordRequired); if (match.Success) { this.version = match.Groups["Version"].Value; if (version == MessageParser.GNTP_SUPPORTED_VERSION) { string d = match.Groups["Directive"].Value; if (Enum.IsDefined(typeof(RequestType), d)) { this.directive = (RequestType)Enum.Parse(typeof(RequestType), d); // check for supported but not allowed requests if (this.directive == RequestType.SUBSCRIBE && !this.allowSubscriptions) { OnError(ErrorCode.NOT_AUTHORIZED, ErrorDescription.SUBSCRIPTIONS_NOT_ALLOWED); } else { this.encryptionAlgorithm = Cryptography.GetEncryptionType(match.Groups["EncryptionAlgorithm"].Value); this.ivHex = (match.Groups["IV"] != null ? match.Groups["IV"].Value : null); if (!String.IsNullOrEmpty(this.ivHex)) this.iv = Cryptography.HexUnencode(this.ivHex); string keyHash = match.Groups["KeyHash"].Value.ToUpper(); bool authorized = false; // Any of the following criteria require a password: // 1. the request did not originate on the local machine or LAN // 2. the request came from the LAN, but LAN passwords are required // 2. it is a SUBSCRIBE request (all subscriptions require a password) // 3. the user's preferences require even local requests to supply a password // Additionally, even if a password is not required, it will be validated if the // sending appplication includes one string errorDescription = ErrorDescription.INVALID_KEY; if (this.passwordRequired || this.directive == RequestType.SUBSCRIBE || !String.IsNullOrEmpty(keyHash)) { if (String.IsNullOrEmpty(keyHash)) { errorDescription = ErrorDescription.MISSING_KEY; } else { string keyHashAlgorithmType = match.Groups["KeyHashAlgorithm"].Value; this.keyHashAlgorithm = Cryptography.GetKeyHashType(keyHashAlgorithmType); string salt = match.Groups["Salt"].Value.ToUpper(); authorized = this.passwordManager.IsValid(keyHash, salt, this.keyHashAlgorithm, this.encryptionAlgorithm, out this.key); } } else { authorized = true; this.key = Key.None; } if (authorized) { if (this.encryptionAlgorithm == Cryptography.SymmetricAlgorithmType.PlainText) { tag = HEADER_TAG; ContinueProcessing(); } else { tag = ENCRYPTED_HEADERS_TAG; ContinueProcessing(); } } else { OnError(ErrorCode.NOT_AUTHORIZED, errorDescription); } } } else { OnError(ErrorCode.INVALID_REQUEST, ErrorDescription.UNSUPPORTED_DIRECTIVE, d); } } else { OnError(ErrorCode.UNKNOWN_PROTOCOL_VERSION, ErrorDescription.UNSUPPORTED_VERSION, version); } } else { OnError(ErrorCode.UNKNOWN_PROTOCOL, ErrorDescription.MALFORMED_REQUEST); } } else if (tag == HEADER_TAG) { if (s == MessageParser.BLANK_LINE) { // if this is a REGISTER message, check Notifications-Count value // to see how many notification sections to expect if (this.directive == RequestType.REGISTER) { if (this.expectedNotifications > 0) { tag = NOTIFICATION_TYPE_TAG; ContinueProcessing(); } else { // a REGISTER request with no notifications is not valid OnError(ErrorCode.INVALID_REQUEST, ErrorDescription.NO_NOTIFICATIONS_REGISTERED); } } else { // otherwise, check the number of resource pointers we got and start reading those this.pointersExpected = GetNumberOfPointers(); if (this.pointersExpected > 0) { this.pointersExpectedRemaining = this.pointersExpected; this.currentPointer = 1; tag = RESOURCE_HEADER_TAG; ContinueProcessing(); } else { OnMessageParsed(); } } } else { Header header = Header.ParseHeader(s); if (header != null) { bool addHeader = true; if (header.Name == Header.APPLICATION_NAME) { this.applicationName = header.Value; } if (header.Name == Header.NOTIFICATIONS_COUNT) { this.expectedNotifications = Convert.ToInt32(header.Value); this.expectedNotificationsRemaining = this.expectedNotifications; this.currentNotification = 1; } if (header.Name == Header.NOTIFICATION_CALLBACK_CONTEXT) { this.callbackData = header.Value; } if (header.Name == Header.NOTIFICATION_CALLBACK_CONTEXT_TYPE) { this.callbackDataType = header.Value; } if (header.Name == Header.NOTIFICATION_CALLBACK_TARGET || header.Name == Header.NOTIFICATION_CALLBACK_CONTEXT_TARGET) // left in for compatibility { this.callbackUrl = header.Value; } if (header.Name == Header.RECEIVED) { this.requestInfo.PreviousReceivedHeaders.Add(header); addHeader = false; } if (addHeader) this.headers.AddHeader(header); } tag = HEADER_TAG; ContinueProcessing(); } } else if (tag == NOTIFICATION_TYPE_TAG) { if (s == MessageParser.BLANK_LINE) { this.expectedNotificationsRemaining--; if (this.expectedNotificationsRemaining > 0) { this.currentNotification++; tag = NOTIFICATION_TYPE_TAG; ContinueProcessing(); } else { // otherwise, check the number of resource pointers we got and start reading those this.pointersExpected = GetNumberOfPointers(); if (this.pointersExpected > 0) { this.pointersExpectedRemaining = this.pointersExpected; this.currentPointer = 1; tag = RESOURCE_HEADER_TAG; ContinueProcessing(); } else { OnMessageParsed(); } } } else { if (this.notificationsToBeRegistered.Count < this.currentNotification) { this.notificationsToBeRegistered.Add(new HeaderCollection()); } Header header = Header.ParseHeader(s); this.notificationsToBeRegistered[this.currentNotification - 1].AddHeader(header); tag = NOTIFICATION_TYPE_TAG; ContinueProcessing(); } } else if (tag == RESOURCE_HEADER_TAG) { if (s == MessageParser.BLANK_LINE) { // we should have found an Identifier header and Length header, or we are just starting a new section Pointer p = this.pointers[this.currentPointer - 1]; if (p.Identifier != null && p.Length > 0) { // read #of bytes int length = this.pointers[this.currentPointer - 1].Length; tag = RESOURCE_TAG; ContinueProcessing(); } else { tag = RESOURCE_HEADER_TAG; ContinueProcessing(); } } else { Header header = Header.ParseHeader(s); // should be Identifer or Length if (header != null) { bool validHeader = false; if (header.Name == Header.RESOURCE_IDENTIFIER) { this.pointers[this.currentPointer - 1].Identifier = header.Value; validHeader = true; } else if (header.Name == Header.RESOURCE_LENGTH) { this.pointers[this.currentPointer - 1].Length = Convert.ToInt32(header.Value); validHeader = true; } else { OnError(ErrorCode.INVALID_REQUEST, ErrorDescription.UNRECOGNIZED_RESOURCE_HEADER, header.Name); } if (validHeader) { tag = RESOURCE_HEADER_TAG; ContinueProcessing(); } } else { OnError(ErrorCode.INVALID_REQUEST, ErrorDescription.UNRECOGNIZED_RESOURCE_HEADER); } } } else if (tag == RESOURCE_TAG) { // deal with data bytes byte[] bytes = this.key.Decrypt(data.ByteArray, this.iv); Pointer pointer = this.pointers[this.currentPointer - 1]; pointer.ByteArray = bytes; BinaryData binaryData = new BinaryData(pointer.Identifier, pointer.ByteArray); ResourceCache.Add(this.applicationName, binaryData); this.pointersExpectedRemaining--; if (this.pointersExpectedRemaining > 0) { this.currentPointer++; tag = RESOURCE_HEADER_TAG; ContinueProcessing(); } else { OnMessageParsed(); } } else if (tag == ENCRYPTED_HEADERS_TAG) { // see if a length was specified (the original spec did not require the main encrypted headers portion to specify a length) if (s.StartsWith(Header.RESOURCE_LENGTH)) { Header header = Header.ParseHeader(s); if (header != null) { int len = Convert.ToInt32(header.Value); tag = ENCRYPTED_HEADERS_TAG; ContinueProcessing(); } } ParseEncryptedMessage(data.ByteArray); if (this.pointersExpected > 0) { tag = RESOURCE_HEADER_TAG; ContinueProcessing(); } else { OnMessageParsed(); } } else { OnError(ErrorCode.INVALID_REQUEST, ErrorDescription.MALFORMED_REQUEST); } } catch (GrowlException gEx) { OnError(gEx.ErrorCode, gEx.Message, gEx.AdditionalInfo); } catch (Exception ex) { OnError(ErrorCode.INVALID_REQUEST, ErrorDescription.MALFORMED_REQUEST, ex.Message); } }
/* // TODO: do we ever want to encrypt responses? public MessageBuilder(ResponseType messageType, Key key, Cryptography.HashAlgorithmType keyHashAlgorithm, Cryptography.SymmetricAlgorithmType encryptionAlgorithm) : this("-" + messageType.ToString(), key, false, Cryptography.HashAlgorithmType.MD5, Cryptography.SymmetricAlgorithmType.PlainText) { } * */ /// <summary> /// Creates a new instance of the <see cref="MessageBuilder"/> class /// </summary> /// <param name="messageType">The type of message (directive)</param> /// <param name="key">The <see cref="Key"/> used to authorize and encrypt the message</param> /// <param name="includeKeyHash">Indicates if the key hash should be included in the message or not</param> protected MessageBuilder(string messageType, Key key, bool includeKeyHash) : base() { this.messageType = messageType; this.key = (key != null ? key : Key.None); this.includeKeyHash = (includeKeyHash && (key != null && key != Key.None)); }
/// <summary> /// Creates a new instance of the <see cref="MessageBuilder"/> class /// used to build a request message. /// </summary> /// <param name="messageType">The <see cref="RequestType"/> of the message</param> /// <param name="key">The <see cref="Key"/> used to authorize and encrypt the message</param> public MessageBuilder(RequestType messageType, Key key) : this(messageType.ToString(), key, true) { }
/// <summary> /// Compares the provides keyHash and salt to the supplied password to see if they are a match. /// </summary> /// <param name="password">The password to compare to.</param> /// <param name="keyHash">The hex-encoded key hash</param> /// <param name="salt">The hex-encoded salt value</param> /// <param name="hashAlgorithm">The <see cref="Cryptography.HashAlgorithmType"/> used to generate the key hash</param> /// <param name="encryptionAlgorithm">The <see cref="Cryptography.SymmetricAlgorithmType"/> used by this key to do encryption/decryption</param> /// <param name="matchingKey">If a match is found, returns the matching <see cref="Key"/>;if no match is found, returns <c>null</c>.</param> /// <returns> /// <c>true</c> if the key hash and salt match the password; /// <c>false</c> otherwise /// </returns> public static bool Compare(string password, string keyHash, string salt, Cryptography.HashAlgorithmType hashAlgorithm, Cryptography.SymmetricAlgorithmType encryptionAlgorithm, out Key matchingKey) { matchingKey = null; if (!String.IsNullOrEmpty(password)) { byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(password); byte[] saltBytes = Cryptography.HexUnencode(salt); byte[] keyBasis = new byte[passwordBytes.Length + saltBytes.Length]; Array.Copy(passwordBytes, 0, keyBasis, 0, passwordBytes.Length); Array.Copy(saltBytes, 0, keyBasis, passwordBytes.Length, saltBytes.Length); byte[] keyBytes = Cryptography.ComputeHash(keyBasis, hashAlgorithm); byte[] keyHashBytes = Cryptography.ComputeHash(keyBytes, hashAlgorithm); string actualKeyHash = Cryptography.HexEncode(keyHashBytes); if (keyHash == actualKeyHash) { matchingKey = new Key(); matchingKey.password = password; matchingKey.salt = salt; matchingKey.keyHash = keyHash; matchingKey.hashAlgorithm = hashAlgorithm; matchingKey.encryptionKey = keyBytes; matchingKey.encryptionAlgorithm = encryptionAlgorithm; return true; } } return false; }
/// <summary> /// Checks the supplied <paramref name="keyHash"/> against all of the stored passwords to /// see if the hash is valid, and retuns the matching <see cref="Key"/> if a match is found. /// </summary> /// <param name="keyHash">The hex-encoded hash to validate</param> /// <param name="salt">The hex-encoded salt value</param> /// <param name="hashAlgorithm">The <see cref="Cryptography.HashAlgorithmType"/> used to generate the hash</param> /// <param name="encryptionAlgorithm">The <see cref="Cryptography.SymmetricAlgorithmType"/> used by this key to do encryption/decryption</param> /// <param name="matchingKey">Contains the matching <see cref="Key"/> if a match is found</param> /// <returns> /// <c>true</c> if the hash matches one of the stored password/key values; /// <c>false</c> if no match is found /// If no match is found, <paramref name="matchingKey"/> will return <c>null</c>. /// </returns> public bool IsValid(string keyHash, string salt, Cryptography.HashAlgorithmType hashAlgorithm, Cryptography.SymmetricAlgorithmType encryptionAlgorithm, out Key matchingKey) { matchingKey = null; if (String.IsNullOrEmpty(keyHash)) return false; keyHash = keyHash.ToUpper(); foreach (Password password in this.passwords.Values) { bool match = Key.Compare(password.ActualPassword, keyHash, salt, hashAlgorithm, encryptionAlgorithm, out matchingKey); if (match) { return true; } } return false; }
/// <summary> /// Initializes a new instance of the <see cref="SubscriberKey"/> class. /// </summary> /// <param name="key">The <see cref="Key"/> to base this key upon.</param> /// <param name="subscriberID">The unique subscriber ID</param> /// <param name="hashAlgorithm">The <see cref="Cryptography.HashAlgorithmType"/> used when hashing values</param> /// <param name="encryptionAlgorithm">The <see cref="Cryptography.SymmetricAlgorithmType"/> used when encrypting values</param> public SubscriberKey(Key key, string subscriberID, Cryptography.HashAlgorithmType hashAlgorithm, Cryptography.SymmetricAlgorithmType encryptionAlgorithm) : base(key.Password + subscriberID, hashAlgorithm, encryptionAlgorithm) { }