/// <summary> /// Builds an unsigned DKIM-Signature header. Note that the returned /// header will NOT have a CRLF at the end. /// </summary> /// <param name="bodyHash">The hash of the body.</param> /// <returns>The unsigned DKIM-Signature header.</returns> private string GetUnsignedDkimHeader(DomainElement domain, string bodyHash) { return string.Format( CultureInfo.InvariantCulture, "DKIM-Signature: v=1; a={0}; s={1}; d={2}; c={3}/{4}; q=dns/txt; h={5}; bh={6}; b=;", this.hashAlgorithmDkimCode, domain.getSelector(), domain.getDomain(), this.headerCanonicalization.ToString().ToLower(), this.bodyCanonicalization.ToString().ToLower(), string.Join(" : ", this.eligibleHeaders.OrderBy(x => x, StringComparer.Ordinal).ToArray()), bodyHash); }
/// <summary> /// Button "save" in domain configuration have been click /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btDomainSave_Click(object sender, EventArgs e) { if (this.epvDomainSelector.GetError(txtDomainName) == "" && this.epvDomainSelector.GetError(txtDomainSelector) == "") { DomainElement oCurrentDomain; bool bAddToList = false; if (this.lbxDomains.SelectedItem != null) { oCurrentDomain = (DomainElement)this.lbxDomains.SelectedItem; } else { oCurrentDomain = new DomainElement(); bAddToList = true; } oCurrentDomain.Domain = this.txtDomainName.Text; oCurrentDomain.Selector = this.txtDomainSelector.Text; oCurrentDomain.PrivateKeyFile = this.txtDomainPrivateKeyFilename.Text; if (bAddToList) { this.oConfig.Domains.Add(oCurrentDomain); this.lbxDomains.Items.Add(oCurrentDomain); this.lbxDomains.SelectedItem = oCurrentDomain; } if (this.SaveDkimSignerConfig()) { this.btDomainSave.Enabled = false; this.btDomainDelete.Enabled = true; } } else { MessageBox.Show("You first need to fix the errors in your domain configuration before saving.", "Config error", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
/// <summary> /// Gets the version of the DKIM-Signature header with the signature appended, along with /// the CRLF. /// </summary> /// <param name="unsignedDkimHeader">The unsigned DKIM header, to use as a template.</param> /// <param name="canonicalizedHeaders">The headers to be included as part of the signature.</param> /// <returns>The signed DKIM-Signature header.</returns> private string GetSignedDkimHeader(DomainElement domain, string unsignedDkimHeader, IEnumerable<string> canonicalizedHeaders) { byte[] signatureBytes; string signatureText; StringBuilder signedDkimHeader; if (domain.CryptoProvider == null) throw new Exception("CryptoProvider for domain " + domain.getDomain() + " is null."); using (var stream = new MemoryStream()) { using (var writer = new StreamWriter(stream)) { foreach (var canonicalizedHeader in canonicalizedHeaders) writer.Write(canonicalizedHeader); if (this.headerCanonicalization == DkimCanonicalizationKind.Relaxed) { unsignedDkimHeader = Regex.Replace(unsignedDkimHeader, @" ?: ?", ":"); string[] temp = unsignedDkimHeader.Split(new char[] { ':' }, 2); unsignedDkimHeader = temp[0].ToLower() + ":" + temp[1]; } writer.Write(unsignedDkimHeader); writer.Flush(); stream.Seek(0, SeekOrigin.Begin); // Why not pass this.hashAlgorithm here, since we already have it? If we're supporting // Exchange 2007, then we're stuck on CLR 2.0. The SHA-256 functionality was added in // .NET 3.5 SP1, but it was done in such a way that the switch statement used internally // by the Crypto .NET classes won't recognize the new SHA256CryptoServiceProvider type. // So, we have to use the string method instead. More details available at // http://blogs.msdn.com/b/shawnfa/archive/2008/08/25/using-rsacryptoserviceprovider-for-rsa-sha256-signatures.aspx signatureBytes = domain.CryptoProvider.SignData(stream, this.hashAlgorithmCryptoCode); } } signatureText = Convert.ToBase64String(signatureBytes); signedDkimHeader = new StringBuilder(unsignedDkimHeader.Substring(0, unsignedDkimHeader.Length - 1)); signedDkimHeader.Append(signatureText); signedDkimHeader.Append(";\r\n"); return signedDkimHeader.ToString(); }
/// <summary> /// Returns a value indicating whether or not the unsigned MIME message in the /// given stream can be signed. In this case, we iterate until we see the From: /// header, and then we only sign it if our domain matches the domain of the From: /// address. /// </summary> /// <param name="inputStream">The input stream.</param> /// <returns>The output stream.</returns> public string CanSign(DomainElement domain, Stream inputStream) { if (this.disposed) throw new ObjectDisposedException("Exchange DkimSigner disposed."); inputStream.Seek(0, SeekOrigin.Begin); // Generate the hash for the body var bodyHash = this.GetBodyHash(inputStream); var unsignedDkimHeader = this.GetUnsignedDkimHeader(domain, bodyHash); // Generate the hash for the header var canonicalizedHeaders = this.GetCanonicalizedHeaders(inputStream); var signedDkimHeader = this.GetSignedDkimHeader(domain, unsignedDkimHeader, canonicalizedHeaders); return signedDkimHeader; }
/// <summary> /// Builds an unsigned DKIM-Signature header. Note that the returned /// header will NOT have a CRLF at the end. /// </summary> /// <param name="bodyHash">The hash of the body.</param> /// <returns>The unsigned DKIM-Signature header.</returns> private string GetUnsignedDkimHeader(string bodyHash, DomainElement domain) { return string.Format( CultureInfo.InvariantCulture, "DKIM-Signature: v=1; a={0}; s={1}; d={2}; c=simple/simple; q=dns/txt; h={3}; bh={4}; b=;", this.hashAlgorithmDkimCode, domain.Selector, domain.Domain, string.Join(" : ", this.eligibleHeaders.OrderBy(x => x, StringComparer.Ordinal).ToArray()), bodyHash); }
/// <summary> /// Initializes various settings based on configuration. /// </summary> private void Initialize() { if (RegistryHelper.Open(@"Exchange DkimSigner") != null) { DkimAlgorithmKind signingAlgorithm = DkimAlgorithmKind.RsaSha1; DkimCanonicalizationKind headerCanonicalization = DkimCanonicalizationKind.Simple; DkimCanonicalizationKind bodyCanonicalization = DkimCanonicalizationKind.Simple; IEnumerable<string> headersToSign = null; // Load the log level. DkimSigningRoutingAgentFactory.logLevel = 0; try { string temp = RegistryHelper.Read("LogLevel", @"Exchange DkimSigner"); if (temp != null) DkimSigningRoutingAgentFactory.logLevel = Convert.ToInt32(temp); } catch (FormatException) { } catch (OverflowException) { } if (logLevel == 0) throw new ConfigurationErrorsException(Resources.DkimSigningRoutingAgentFactory_BadLogLevel); // Load the signing algorithm. try { signingAlgorithm = (DkimAlgorithmKind)Enum.Parse(typeof(DkimAlgorithmKind), RegistryHelper.Read("Algorithm", @"Exchange DkimSigner\DKIM"), true); } catch (Exception ex) { throw new ConfigurationErrorsException(Resources.DkimSigningRoutingAgentFactory_BadDkimAlgorithmConfig, ex); } // Load the header canonicalization algorithm. try { headerCanonicalization = (DkimCanonicalizationKind)Enum.Parse(typeof(DkimCanonicalizationKind), RegistryHelper.Read("HeaderCanonicalization", @"Exchange DkimSigner\DKIM"), true); } catch (Exception ex) { throw new ConfigurationErrorsException(Resources.DkimSigningRoutingAgentFactory_BadDkimCanonicalizationHeaderConfig, ex); } // Load the body canonicalization algorithm. try { bodyCanonicalization = (DkimCanonicalizationKind)Enum.Parse(typeof(DkimCanonicalizationKind), RegistryHelper.Read("BodyCanonicalization", @"Exchange DkimSigner\DKIM"), true); } catch (Exception ex) { throw new ConfigurationErrorsException(Resources.DkimSigningRoutingAgentFactory_BadDkimCanonicalizationBodyConfig, ex); } // Load the list of headers to sign in each message. string unparsedHeaders = RegistryHelper.Read("HeadersToSign", @"Exchange DkimSigner\DKIM"); if (unparsedHeaders != null) { headersToSign = unparsedHeaders.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); } // Load the list of domains domainSettings = new List<DomainElement>(); string[] domainNames = RegistryHelper.GetSubKeyName(@"Exchange DkimSigner\Domain"); if (domainNames != null) { foreach (string domainName in domainNames) { string selector = RegistryHelper.Read("Selector", @"Exchange DkimSigner\Domain\" + domainName); string privateKeyFile = RegistryHelper.Read("PrivateKeyFile", @"Exchange DkimSigner\Domain\" + domainName); DomainElement domainElement = new DomainElement(domainName, selector, privateKeyFile); if (domainElement.initElement(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))) { domainSettings.Add(domainElement); } } } this.dkimSigner = new DkimSigner( signingAlgorithm, headerCanonicalization, bodyCanonicalization, headersToSign); Logger.LogInformation("Exchange DKIM started. Signing Algorithm: " + signingAlgorithm.ToString() + ", Canonicalization Header Algorithm: " + headerCanonicalization.ToString() + ", Canonicalization Header Algorithm: " + bodyCanonicalization.ToString() + ", Number of domains: " + domainSettings.Count); } else { throw new ConfigurationErrorsException(Resources.DkimSigningRoutingAgentFactory_BadDkimConfig); } }
/// <summary> /// Returns a value indicating whether or not the unsigned MIME message in the /// given stream can be signed. In this case, we iterate until we see the From: /// header, and then we only sign it if our domain matches the domain of the From: /// address. /// </summary> /// <param name="inputStream">The input stream.</param> /// <returns>The output stream.</returns> public string CanSign(DomainElement domain, Stream inputStream) { if (this.disposed) { throw new ObjectDisposedException("Exchange DkimSigner disposed."); } inputStream.Seek(0, SeekOrigin.Begin); // Generate the hash for the body Logger.LogDebug("Creating body hash"); string bodyHash = this.GetBodyHash(inputStream); Logger.LogDebug("Got body hash: " + bodyHash); string unsignedDkimHeader = this.GetUnsignedDkimHeader(domain, bodyHash); // Generate the hash for the header Logger.LogDebug("Creating signing header"); IEnumerable<string> canonicalizedHeaders = this.GetCanonicalizedHeaders(inputStream); string signedDkimHeader = this.GetSignedDkimHeader(domain, unsignedDkimHeader, canonicalizedHeaders); Logger.LogDebug("Got signing header: " + signedDkimHeader); return signedDkimHeader; }