Signing mail with DKIM

Signing mail with DKIM

This tutorial will demonstrate how you can sign your outgoing emails with DKIM (DKIM is based on Yahoo’s Domain keys and Cisco’s Identified Internet mail, It is defined in RFC 4871.)

The goal of this tutorial is to have postfix sign all mails for multiple domain names, including mail that originates from PHP’s mail function, or any other that passes the Postfix or “Postfix / Sendmail” MTA.

Debian Lenny is my Operating System, Debian’s dkim-filter’s package will sign the mail, and Debian’s Postfix package will be our MTA
1- About DKIM / How it works.

Just like SSL, DKIM uses a private key and a public key to encrypt messages.

in SSL (and DKIM) messages encrypted with the private key are only decrypted by the public key and the messages encrypted by the public key can only be decrypted by the private key.

The private key is (as in SSL) stored on the server ONLY, this is why it is private, and disclosing it to anyone else defeats the purpose, and the public key, in DKIM is stored IN DNS for the receiving server to obtain with a simple DNS request to the nameservers…

A text record in DNS will have the selector as the record name, and the key as record value.

Anyone can decrypt the message by looking up the DNS record, then using that to decrypt the message, the idea is based on the following logic.

The Logic: the sender managed to encrypt this message, so the sender must be the authentic domain owner to be able to encrypt with the private key.

2- Installing DKIM

On my Debian Lenny machine with Postfix installed

apt-get install postfix dkim-filter

3- generating Private and Public keys

Now, with our dkim-filter package, dkim-genkey is automatically installed

So our domains are example1.com example2.com example3.com and example4.com, we want to sign all emails for all mentioned domain names, In our example, multiple domains on our server will use the same public and private keys !

dkim-genkey -d example1.com -t -s dkmail

dkmail that you see at the end is the selector, you can have many selectors for more than one mail server where you do not want to share the private key between servers.

in the above, we are generating a public and a private key into 2 files in the current directory, although the command includes a domain name, the keys can be used for any set of domain names really, so not to worry about that, Now, let us create a directory to store the Private Key, and move the private key to it

mkdir /var/dkim_keys
mv dkmail.private /var/dkim_keys/dkim.private

The public key is stored in the file dkmail.txt, the contents of that file are 1 line that is as follows

dkmail._domainkey IN TXT "v=DKIM1; g=*; k=rsa; t=y; p=MIGfMA0GC...AQAB" ; ----- DKIM dkmail for anyoneofyourdomains.com

While this is a typical TXT record in BIND for example, adding this to your DNS will depend on your provider, for example when using godaddy, you can click add TXT record in your DNS manager, and enter the following into the 3 provided fields

the TXT name should be

dkmail._domainkey

The TXT value should be (The dots denote a longer string, this is just an example)

v=DKIM1; g=*; k=rsa; t=y; p=MIGfMA0GC...AQAB

And the TTL should be left at

1 hour

Now, once you hit OK, you can use any linux machine that has the DIG command to make sure your changes are already visible on the internet by issuing

dig dkmail._domainkey.anyoneofyourdomains.com TXT

You should see your public key as a result, once you do, you can move on to the next steps.

Now, Edit the file /etc/default/dkim-filter and add the following line at the bottom

SOCKET="inet:8891@localhost"

this will make the application listen on port 8891 which we will use with postfix

The other config file is /etc/dkim-filter.conf

in that file i have the following settings, you can change that if you like, my file uses the DKIM-FILTER to sign multiple domain names with the same private key, the public key is applied to all domain name DNS of all 4 domains

# Log to syslog
Syslog			yes
# Required to use local socket with MTAs that access the socket as a non-
# privileged user (e.g. Postfix)
UMask			002

# Sign for example.com with key in /etc/mail/dkim.key using
# selector '2007' (e.g. 2007._domainkey.example.com)
Domain			example1.com,example2.com,example3.com,example4.com
KeyFile		/var/dkim_keys/dkim.private
Selector		dkmail


# Common settings. See dkim-filter.conf(5) for more information.
AutoRestart		yes
Background		yes
Canonicalization	simple
DNSTimeout		5
Mode			sv
SignatureAlgorithm	rsa-sha256
SubDomains		no
#ASPDiscard		no
#Version		rfc4871
X-Header		no

###############################################
# Other (less-standard) configuration options #
###############################################
# 
# If enabled, log verification stats here
#Statistics		/var/run/dkim-filter/dkim-stats
#
# KeyList is a file containing tuples of key information. Requires
# KeyFile to be unset. Each line of the file should be of the format:
#    sender glob:signing domain:signing key file
# Blank lines and lines beginning with # are ignored. Selector will be
# derived from the key's filename.
#KeyList		/etc/dkim-keys.conf
#
# If enabled, will generate verification failure reports for any messages
# that fail signature verification. These will be sent to the r= address
# in the policy record, if any.
#SendReports		yes
#
# If enabled, will issue a Sendmail QUARANTINE for any messages that fail
# signature verification, allowing them to be inspected later.
#Quarantine		yes
#
# If enabled, will check for required headers when processing messages.
# At a minimum, that means From: and Date: will be required. Messages not
# containing the required headers will not be signed or verified, but will
# be passed through
#RequiredHeaders	yes

Now, in your postfix installation, add the following lines at the bottom of the /etc/postfix/main.cf file

# DKIM
milter_default_action = accept
milter_protocol = 2
smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891

Now, you should restart both postfix and dkim-filter

/etc/init.d/dkim-filter restart  
/etc/init.d/postfix restart  

Now, if you send an email to Yahoo mail, or Gmail, you can view the headers and make sure that your DKIM state is pass, if not you should recheck.

Note that SPF goes very well with DKIM and should also be implemented

1- Where are log files for help…

Links for this inp progress article

http://packages.debian.org/source/lenny/dkim-milter

http://packages.debian.org/lenny/dkim-filter

http://www.topdog.za.net/postfix_dkim_milter

http://www.elandsys.com/resources/sendmail/dkim.html

https://help.ubuntu.com/community/Postfix/DKIM

https://help.ubuntu.com/community/Postfix/DomainKeys

https://help.ubuntu.com/community/Postfix

http://www.howtoforge.com/postfix-dkim-with-dkim-milter-centos5.1

http://www.sendmail.com/sm/wp/dkim/

http://www.howtoforge.com/forums/showthread.php?p=234174

http://www.howtoforge.com/set-up-dkim-for-multiple-domains-on-postfix-with-dkim-milter-2.8.x-centos-5.3

http://www.howtoforge.com/forums/showthread.php?p=231268

http://howtoforge.org/set-up-dkim-on-postfix-with-dkim-milter-centos-5.2

http://howtoforge.org/forums/showthread.php?t=26219

Godaddy credits expire before being activated

I just got off the phone with Godaddy, as usual, i have my “strange godaddy thing of the day”, i have just learned that i lost my $125 of Google advertising coupons because i did not activate the credit from within my godaddy account within a month of buying the product that got me the credits

What you need to know, don’t save your ad credits for later, even when you don’t activate them, they expire, nothing written on that, they don’t tell you anywhere, but the Google ad credit coupons expire on their own after 1 month of the purchase not of activation.

This time, i login to get a receipt from an old API account i have with godaddy, and godaddy tells me they have Google credits for me, i say yipee, and go to collect them, then i start a new adwords account with my gmail Google account, and then what do you know, Google tells me this credit has expired “This promotional code has expired. Learn more”.

So i go to godaddy’s website and other sources, but it only says that the credit must be used on a new Google account within 15 days of activation. So as you might expect, i call godaddy who will then blame Google (Even though it is a newly activated Google credit)

So, i am listening to the recorded conversation to tell you here what happened,

** for my records and convenience the file from the auto call recorder is godaddy_expired_credits_API_Voice-0003.amr ***

Anyway, here is more or less what happened (i am not strictly transcribing but rather describing and skipping the nonsense), but the audio file is available (I am not sure if it is legal to post it though)

So, here is the lowdown on the phone call, this is what you need to know, i don’t know how godaddy will justify that they still offer you to activate a credit that has expired before being activated, or why they still give you a link to activate it after the 1 month, or why they have the activation feature all together if it starts counting for expiry from the time of purchase not the time of activation of the credit, but check this out.

ON HOLD through 22:36
Godaddy Brit : as far as it expires after a month , that you will not find with us, however you will find on that disclaimer there that Google has the right to revoke it at any time for any reason, any credits that we supply, that may be on Google’s end, that’s not something we have the right to say, because once again that is on Google’s end, Google’s terms, but not our terms.

Waiting for a representative to pick up 6:46

Me: i have an old account, and i logged in to get the invoice from there so i can print it, and i found that i have google credits, so i opened the credits tab where the credits were not used, so i clicked activate, and i just did activate them, and then i went to Google adwords, i created a new account, i entered the promo code, and it tells me that this promotional code has expired, i just activated it but it expired.

Godaddy Brit: Ok let me take a look here, did you ever have any products in this account

Me: Yes, surely, an API reseller account

Godaddy Brit: Okay, but i can’t see anything in here right now.

Me: I can see it, i just click on promo codes, Oh, u mean… You just go to order history, i cancelled that product,

Godaddy Brit: Okay, i am not sure that credit will be useful if there is no actual product in the account

Me: Well Brit, i am not sure about that, because it doesn’t have an expiry on the credit, So if we could go step by step and check out that credit, it shows i still have the credit, and there is a button to activate it, So i clicked the button just right now two minutes ago, and it gave me that it was activated on the fifth, which is today, and gave me the code, Now just by giving me the code this means this is an actual Google code, right ? Google credit code, or is it some random string of numbers

Godaddy Brit: I am not sure how it works in connection with Google, but if you want me to run this by my lead just to make sure, please remain on the line.

On wait 13:15
Godaddy Brit: Apparently those credits, although you were able to activate it, they expire on Google’s end about a month after you purchase the product, so if you bought a domain name,and you got the credits, A month after you could still activate it on our end, but Google will reject it about a month after the purchase

Me: Ok, Brit, is that written anywhere ? because i have seen your adwords account needs to be less than two weeks old, but i leave my credits unactivated because it doesn’t say anywhere that you lose your rights to the credits here or there or this way or that way,

Godaddy Brit: Ok, this is probably not as illustrated the best that it could be but let me see here, (A wait) and are you activating this code in a new adwords account or….

Me: Yes sir, it is a new account

Godaddy Brit: So right now i am scanning through our almost unless looking legal terms of service for you to find you where this statement is on.

Me: No problem, take your time, sorry for the inconvenience

Godaddy Brit: Let me run this by one of my leads

Me: take your time

ON HOLD through 22:36
Godaddy Brit : as far as it expires after a month , that you will not find with us, however you will find on that disclaimer there that Google has the right to revoke it at any time for any reason, any credits that we supply, that may be on Google’s end, that’s not something we have the right to say, because once again that is on Google’s end, Google’s terms, but not our terms.

Common name example.com is already present in a current certificate

Four days of godaddy SSL hell (starfield technologies certificate)

So, i am not writing this to mock godaddy or godaddy resellers or support, this is just a problem that you need to understand before you call godaddy (or any of their resellers) simply to save time and not to have to wait for 4 days like i did

When i submit my security signing request (csr file) to godaddy or wild west domains, the error i get reads

Common name example.com is already present in a current certificate.

The reason to this is that someone (probably you or a previous owner) already issued a certificate for that domain from another account.

SOLUTION: Certificate, or even expired certificate must be REVOKED, cancelled is not good enough, the magic word is REVOKED, when the certificate expires, you can not revoke it, you must contact support and tell them that you need to revoke it by email.

So, i have not taken the time to organize the text below this line yet, if you are arguing about something in an effort to reduce your wait time, see below for whatever you need, but again, i did not refine any text below this line or organized it or even checked that it is correct.

———————————————————————-

UPDATE: Godaddy wrong again, when i get the time i will listen to the recorded conversation (because my phone auto records all conversations) and tell you exactly what you need to do to not rely on the faulty godaddy manuals, in short this is what happened (as i remember it is close to this)

So, here is what my conversations with godaddy comes down to, not accurately, but in short, what it comes down to (for my reference, the file is godaddy ssl Voice-0003.amr)

But as i start to get skeptical about this resolving itself in a few hours, i will call jet (the very helpful customer care representative) again and see if anything can be done.

Godaddy (Jet): After canceling the certificate, you need to wait for three days
Me: No, i am sure we have to revoke it, and since it is expired, i can not revoke it
Godaddy (Jet): No you are mistaken, after cancelling, we wait for three days then put in a new request
Me: Ok i will wait
I wait for 2 days, then call again as my website is down
Me: are you sure that within 3 days the system will do cleanup, if the job runs once every three days, 2 days increases the odds of what i was saying being right, can you please double check ? my website has been down for two days
Godaddy: no need to check, there is nothing we can do
And after 3 days of still no luck, i call again
Me: hi, i have waited for 3 days
Godaddy (denis): yes sir, for a certificate to get cleared from the system it needs to be revoked, i will have them send you an email so we can revoke it by email.
me: Seriously, that’s what i said 3 days ago
Godaddy (denis): I wonder why they did not do that on the first day
Me: thanx anyways

installing proper SSL on apache

You are looking for A-Z instructions, what i am doing here is to show you how to install a godaddy or starfield certificate to a website on apache server on a debian system, if you want the instructions to issue the certificate yourself (self signed certificate), i have covered that in another post, you can adopt this to the system of your choice, here i will explain what i am doing too so that you can adapt it to other systems

Note that you need a dedicated IP address for every website / certificate.
I have apache already installed on debian squeeze and running a website with no SSL

1- Before we begin, you may want to execute

apt-get update

2- Install openSSL, on debian this is done with

apt-get install openssl ssl-cert

3-Create a directory for the keys

mkdir /etc/apache2/sslkeys

4- Creating a PRIVATE key (Give to no one)

Before executing this command
You will be asked to chose a password and enter it twice, please keep this password on a paper close to you since we will need this password to decrypt this key in the following steps, this password is important during this process, no longer important after that.

openssl genrsa -des3 -out /etc/apache2/sslkeys/server.key 2048

5- Create a signing request to give to godaddy or starfieldtech
Before executing this command, remember that from the questions you will face, the only one that is TEHNICALLY IMPORTANT IS to use the common name example.com (not www.example.com), unless it is a subdomain other than www you can use subname.example.com, all other fields you should answer as you would like them to appear to people, but the certificate will not work with an incorrect common name

 openssl req -new -key /etc/apache2/sslkeys/server.key -out /etc/apache2/sslkeys/server.csr

NOTE: we could have created a signing request and a private key in one go with

openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr

But we chose to not do that because this tutorial aims to show you the exact steps and what they do

6- Now, we have a secure signing request, all we need to do is give that to the issuing authority so that they can give us a signed public key

UPDATE: Done with the problem of already present in a current certificate after 4 days of talking to godaddy

Now, i can generate my new certificate, but i waiting for 4 days that i could have done without and got it on the first day, the 72 hours written in the manual is probably the MAXIMUM after revoking a certificate, not after canceling it.

Problem, apache will not start without pass phrase, this also means that rebooting the machine will have the machine hang waiting for apache to start and waiting for a user to enter a password for apache, so we need to decrypt the private key
Please note that this does not make your connection less secure, but in the event that someone gets hold of the key file (That you should protect encrypted or not), they can defeat SSL security.

root@someserver:~#/etc/init.d/apache2 restart
Restarting web server: apache2 ... waiting Apache/2.2.16 mod_ssl/2.2.16 (Pass Phrase Dialog)
Some of your private key files are encrypted for security reasons.
In order to read them you have to provide the pass phrases.

Server www.example.com:443 (RSA)
Enter pass phrase:

OK: Pass Phrase Dialog successful.

Anyway, now we should come back to how to remove the pass phrase from the private key

Assuming that your RSA key is stored in the file
/etc/apache2/sslkeys/server.key
To decrypt the file, so that apache does not requer a password with every restart
1- copy the key file:

cp /etc/apache2/sslkeys/server.key /etc/apache2/sslkeys/server.enc.key

Now, decrypt the key (read from the backup file) into the key file in our config

openssl rsa -in /etc/apache2/sslkeys/server.enc.key -out /etc/apache2/sslkeys/server.key

Now the encrypted key is in the server.enc.key just in case you need it, and the key used by apache is NOT encrypted and is in server.key file (That apache already uses)

PHP execution speed et al

There are many tools that precompile PHP to make it run faster, up to now, my favorite is APC which also serves as a very fast value cache (for persistence between requests), a value cache much faster than memcached (but not as distributed).

For some time, i have been optimizing further by asking APC to never check if the file is modified on the disk, and whenever my software is modified i would manually clear the APC cache so that the whole script can be compiled all over again (I say compiled loosely speaking, in reality, it is simply turned into bytecode).

In any case, when you have a server with plenty of ram, it would be convenient if the PHP engine can read the file itself in byte and skip that step for compiled files, and from the way the linux kernel works, those files would be cached from disk into ram (because when a file is read or written, linux keeps a cache of it in ram).

So, bcompiler should be a good extension to PHP that fits such a criteria, and is probably my new way of running my scripts.

Also, bcompiler hides my source code, but i am not interested in that to protect my intellectual property, usually i am not very concerned about my intellectual property because it takes a very good programmer to understand a program and take things from it, and if the person is such a programmer, well, he can also write his own, and with the help of google, he can arrive at something like what i am doing, so to make a long story short, i am interested in hiding my source code for application security reasons, or Security through obscurity as MircoSfot would put it

Checking if SSD trim is working (discard)

Note that if your kernel is before 2.6.33 you can check, but it won’t be working !

in the case that you don’t want to update your kernel, and you just want to trim your disk, try wiper.sh or fstrim, both are command line tools that you can run manually or put in a cron job. if you do want to update your kernel, here is how on debian squeeze

So for example, if you are on debian 6 squeeze, you need to get a kernel from the backports (add the line “deb http://backports.debian.org/debian-backports squeeze-backports main” to your /etc/apt/sources.list then apt-get update then apt-get -t squeeze-backports install linux-image-3.2.0-0.bpo.2-amd64) to get the new kernel, it will then work.

I assume you already have an ext4 file system with discard option in fstab as described on this website

Also note: Many modern SSDs will not reclaim the TRIMmed space., so if using the test below you see zeros, discard (trim) is working 100%, if you don’t. it may or may not be working… but if you wait fdor a significant amount of time, then reboot, the zeros should appear in that exact location even if the disk does not reclaim instantly … happyt trimming, now to the procedure

now, write a file to the ssd (random numbers)
dd if=/dev/urandom of=/hds/ssd300/myfile.bin bs=1M count=3

Find the location where the file begins
hdparm –fibmap /hds/ssd300/myfile.bin

Now, take note of the start address and use it in this command replacing xxxxx

hdparm –read-sector xxxxxx /dev/sdb
You should see random numbers

Delete the file
rm /hds/ssd300/myfile.bin
Sync with the command
sync

Wait for 2 minutes
the issue the same command to read again
hdparm –read-sector xxxxxx /dev/sdb
You should now see all zeros, if you do not, the disk has not been trimmed 🙂

SSD trim command on linux

I am writing this because the stuff you need is not in one place elsewhere, this is what you really need to know, and i want to keep this very short, if you like you can read more elsewhere, this one will only share what you really should know.

1- Do i need trim ?
For reading NO, so if you write once and read 102112913 times, you are good without trimming anything, without trimming, disk writes are slow, reading is absolutely not affected by trim.

2- What is the difference between the ext4 discard option and running fstrim myself manually every once in a while, or even put it in a cron job ?
on ext4 with trim enabled, blocks are trimmed (erased) whenever they are no longer in use by the file system, meaning, when data is deleted from a block, the physical flash memory is erased right after the data deletion, so your disk will remain trimmed all the time, the overhead is not much because the OS knows the block it just freed, so it simply does no more math other than issue a second command to trim, when you run fstrim, fstrim will read the whole file system, and whenever it finds an empty spot, it will trim (hardware erase).

3- i forgot to enable discard, do i just enable it and all is good, is that safe.
Yes it is safe, but enable it, then manually run fstrim only once, or you can wait, and eventually all spots will be trimmed after the get written to and erased again.

Ext4 (the new linux file system) supports TRIM when you mount the disk with the discard option, you can use tools to trim with ext2 or ext3, but it won’t be automatic and not as efficient.

1- But i want ext2 because i don’t want Journaling
ext2 is in fact ext3 without the journal, in ext4 you can remove the journal as well with no problems at all, there are no consequences, ext4 was designed to run with or without journaling

How do i format the disk in ext 4 and enable trim ?
For instructions on creating ext4 partitions, see here , as for the mounting, the line should have an extra option called discard and it should look something like this in your /etc/fstab

UUID=b7a491b1-a690-468f-882f-fbb4ac0a3b53       /hds/3tb            ext4     defaults,noatime,discard                0       1

You should be done, there is nothing more to do

3- I am stuck with ext2 and i don’t want to move, reformet and then move back again
before that, do you know that you can convert the drive to ext4 ?

4- I don’t want to convert anything to anything, i just want to manually trim
Thats easy, use the command
fstrim /hds/myssddisk

and you are done, but mind you, on anything but ext4, this will trim the whole unused space trimed or not trimmed

WINSCP for linux !

An application for windows that i wantin Linux is WINSCP, but it seems the author of WINSCP says (on his forum) that ” Sorry, there’s no chance for that.”

In any case, i have no doubts there are hundreds of applications that can do the Job, in fact the file browser that comes with your gnome or KDE already opens FTP and SFTP and SCP connections, so you need to look no further.

There are also applications that can mount a remote file system that is run on SSH, xxx is one such software

But truth be told since the days of Norton commander, i have always liked the two window view that winSCP is similar to.

So in this post, i will add screenshots of the applications similar to WINSCP, i will try both krusader and filezilla (Yes, filezilla does support)

apt-get update

then

apt-get install krusader filezilla

With Krusader, it is a good idea to install 
apt-get install kdiff3 kompare xxdiff krename rar unrar zip

Another software to be tested would be snowflake, confusingly it is being renamed to muon, which is already the name of a package manager for debian !, in any case, installing snowflake is as simple as downloading the deb file then installing it

wget https://github.com/subhra74/snowflake/releases/download/v1.0.4/snowflake-1.0.4-setup-amd64.deb

then install it

dpkg -i snowflake-1.0.4-setup-amd64.deb

Worth noting that on my 4K display which has a 200% setting, snowflake is not usable, the font is so small, and clicking on something is a challenge, so to work around this while the maintainers fix this for people who have settings like mine, i run snowflake from the terminal with

java -Dsun.java2d.uiScale=2.5 -jar /opt/snowflake/snowflake.jar

And now we have both, on my computer which is a fresh install, krusader was a 90MB download, in your case, it is probably much less because most of the things downloaded are libraries you probably already have.

in any case, let me take those screenshots of WINSCP’s alternatives and get back to this post

MySQL innodb row count very innacurate

They say the number of rows you see in PHPMyADMIN is approximate and not very precise for innodb tables, i can tell you that depending on how the table has been used, the results can be very very inaccurate and sometimes irrelevant.

phpmyadmin wants to display a row count with the list of tables, in myisam, a value is maintained within the database saying how many rows are in the database, INNODB does not maintain such a value so it has to either respond accurately without regard to the delay and scans required or give an approximate reading, it will depend on how you ask, read on for more details.

Q: why are innodb row counts innacurate in PHPMYADMIN ?

Well, innodb would return an accurate count if it were asked formally with a

SELECT COUNT(*) FROM tablename"

But phpmyadmin can not execute such a statement with every listing of a database, because it means the database engine will have to read the whole database and scan all rows.

Q: in that case how come it works for MyISAM ?

A: MyISAM maintains a number stating how many rows are in there that is incremented and decremented with every insert and delete, so the database engine does not need to scan the whole table to give us a row count.

Q: Then where does PHPMYADMIN get those approximate numbers ?

For speed, PHP MY ADMIN would execute something similar (If not exactly)

SHOW TABLE STATUS WHERE 1;

This is when MyISAM would read it’s internal row count value, and Innodb would return an estimate because it does not maintain such a value.

You can also use the command to see a subset of the tables like this

SHOW TABLE STATUS LIKE 'wp_%';

to see status of all tables starting with ‘wp_’

Q: Why does innodb not maintain such a number

A: probably for performance reasons, to avoid the need to update such a number with every insert and every delete.

athurx.sys causes blue screen of death (BSOD) [SOLVED]

athurx is the atheros wireless driver, i have 2 atheros adapters on the same computer, one is 300Mb/s and the other is 150Mb/s

The reason for the blue screen is that the wireless interface driver(S) on my 64bit windows 7 (could be on any other platform too) is outdated, and it seems the old version of the driver has a problem in using 2 different atheros adapters on the same computer, the solution seems to lie in updating the drivers.

1- The Wireless N 150Mb/s (TP-LINK TL-WN722N, atheros AR9271 chip, should also apply to TL-WN721N that uses the same chip)
So, my atheros based TP-LINK TL-WN722N uses the Qualcomm atheros AR9271 chip, Windows installs driver version 2.0.0.32 dated 1/4/2010, and since Qualcomm atheros does not provide their drivers directly on there website, i downloaded the driver from TP-LINK, the new driver had version 2.0.0.62 and was dated more than a year later 4/20/2011, the problem with this driver is that it is NOT SIGNED, and therefore you have to manually install the driver then agree to install a non signed driver, simply asking windows to update from a directory will not update your driver, also check the driver versions before you update to see if your drivers have been updated.

2- The Wireless N 300Mb/s (tp-link TL-WN821N, Atheros AR7015)
This one also has an outdated driver that can be updated from the TP-LINK website, just like the one above.

Make sure you unplug the other driver as you update the software for the first, and once both have drivers installed, reboot and plug in both adapters, works like a dream up to now.