If this is your first visit, be sure to check out the FAQ by clicking the link above. You may have to register before you can post: click the register link above to proceed. To start viewing messages, select the forum that you want to visit from the selection below. |
|
|
Thread Tools | Display Modes |
#11
|
|||
|
|||
Advice on securing a sensitive Access database
In article
, The Frog Mon, 14 Apr 2008 00:45:10 writes [snip detail] So, crash course in cryptography aside, Thank you for that. It was very clear and I actually understand it! here are some links that I have used for the different algorithms and components: Thank you for all that. I will go through them in the next few days, but it is your first post that I still need to study. Thanks also for your kind offer of help. -- Les Desser (The Reply-to address IS valid) |
#12
|
|||
|
|||
Advice on securing a sensitive Access database
Anytime. Glad I can offer a little help.
Cheers The Frog |
#13
|
|||
|
|||
Advice on securing a sensitive Access database
Hi Les,
I also found the code for the GUID creation, courtesy of Trigeminal. I have used this in Excel and Access before and it seems to work fine :-) Enjoy '------------------------------------------ ' basGuid from http://www.trigeminal.com/code/guids.bas ' You may use this code in your applications, just make ' sure you keep the (c) notice and don't publish it anywhere ' as your own ' Copyright (c) 1999 Trigeminal Software, Inc. All Rights Reserved '------------------------------------------ Option Compare Binary ' Note that although Variants now have ' a VT_GUID type, this type is unsupported in VBA, ' so we must define our own here that will have the same ' binary layout as all GUIDs are expected by COM to ' have. Public Type GUID Data1 As Long Data2 As Integer Data3 As Integer Data4(7) As Byte End Type Public Declare Function StringFromGUID2 Lib "ole32.dll" _ (rclsid As GUID, ByVal lpsz As Long, ByVal cbMax As Long) As Long Public Declare Function CoCreateGuid Lib "ole32.dll" _ (rclsid As GUID) As Long '------------------------------------------------------------ ' StGuidGen ' ' Generates a new GUID, returning it in canonical ' (string) format '------------------------------------------------------------ Public Function StGuidGen() As String Dim rclsid As GUID If CoCreateGuid(rclsid) = 0 Then StGuidGen = StGuidFromGuid(rclsid) End If End Function '------------------------------------------------------------ ' StGuidFromGuid ' ' Converts a binary GUID to a canonical (string) GUID. '------------------------------------------------------------ Public Function StGuidFromGuid(rclsid As GUID) As String Dim rc As Long Dim stGuid As String ' 39 chars for the GUID plus room for the Null char stGuid = String$(40, vbNullChar) rc = StringFromGUID2(rclsid, StrPtr(stGuid), Len(stGuid) - 1) StGuidFromGuid = Left$(stGuid, rc - 1) End Function Cheers The Frog |
#14
|
|||
|
|||
Advice on securing a sensitive Access database
In article
, " Tue, 15 Apr 2008 00:23:40 writes I also found the code for the GUID creation, courtesy of Trigeminal. I have used this in Excel and Access before and it seems to work fine :-) Thank you. I have read trough your first post again and am having some difficulty understanding it all. It would be helpful to get an overview of what is being achieved. For starters, is the following correct? 1. Relevant tables in the data mdb are individually encrypted by encrypting each relevant field. 2. Decryption keys are stored, encrypted, in the front-end db (does it have to be separate from the application front-end?) 3. Access to the decryption keys is controlled by some user entered password. 4. It seems obvious that any field that has been encrypted can no longer be directly bound to an Access control. It must be displayed via a function and updated by code. Point 4 is not a problem as it is only limited data that needs encryption. -- Les Desser (The Reply-to address IS valid) |
#15
|
|||
|
|||
Advice on securing a sensitive Access database
Hi Les,
Lets see if we can go through this step by step so to speak. I will attempt to answer each point in turn as we go, and expand on the actual methodology I used. 1. *Relevant tables in the data mdb are individually encrypted by encrypting each relevant field. What I did here was to build a function that wasa used for both encryption and decryption of a field, based on the AES algorithm. Effectively you can choose how many AES keys are used for securing the data. I used one AES key per table. I applied the function to encrypt / decrypt such that any data read from the table was unintelligible without decrypting it through the function, and in turn no data was written to the table without being encrypted with the function. In this way each field was encrypted with the same AES key. This was done through unbound forms and code. The end user never actually saw the encryption taking place. The AES key for each table was stored as a table property with DAO code, encrypted Asymmetrically (public / private), for each user that had access to the table. Each users public key was used to encrypt a copy of the AES key for the table and store that as the value for the property. Only the users private key would be able to decrypt the copy of the AES key stored as a property specific to that user (eg/ I made a table property with the same name as the username, and stored the encrypted (users public key) form of the AES key as the value of that property). 2. *Decryption keys are stored, encrypted, in the front-end db (does it have to be separate from the application front-end?) I actually kept all the encryption keys stored in the back end, and only the functionality to use them in the front end. This way the front end never really needed changing once the encrypt / decrypt functionality had been built in. Everything stored in the backend db was in ciphertext (encrypted form). This way it did not matter if someone stole a copy of the backend db, it was effectively useless. If they connected via code from a different application to try and read the data all they would get is meaningless rubbish from each field. I made a third application specific to the front end / back end application that was specifically used to administer the cryptography. In this third application I had the ability to connect to the back end and add users (and the associated encrypted keys) to the tables. In this Crypto Admin app I kept stored the AES keys for the tables, as well as the public and private key pairs for each users asymmetric keys. I also kept a 'master' public / private key pair, which I associated the same as a user, to each table. This was a type of failsafe in case I had to do some form of data recovery. The master user had no login to the normal application though. In theory you dont actually need it because you have the AES keys anyway, so you could leave it out. I also built a function into the Crypto Admin application to be able to dump the data into a non encrypted database if necessary. I was never truly comfortable with the function as I felt that it was dangerous to have this potentially in the wrong hands, but the bosses wanted it (sigh). I also made sure that I had a log table built into the backend, and into the crypto admin app, so that all user activity was recorded. I dont know if you need to go this far or not, but it is a useful idea if you are tracking attempted breaches. I kept the Crypto Admin application completely separate from the network, it lived (lives) on a secure (password to access) USB key, and a backup of the AES keys is printed out and stored in a safe, along with a copy of all the code, and a CD with empty versions of the finished apps. Oh yeah, I almost forgot. I also built in to the Crypto Admin app the ability to change the AES keys for each table in case they were felt to be compromised. I did this by having the app simply create a new db with the appropriate table structures, and then quite literally read each row from the source, decrypt it with the old key, encrypt the data with the new key, and store it in the new backend db. Needless to say this was a time consuming process but a nice safety feature to have. 3. *Access to the decryption keys is controlled by some user entered password. The user access to each table was done via checking if the user had a table property in their name, with a stored AES key value. This was also able to be checked for validity (ie/ to see if someone had just copied the property from another table). The way the user asymmetric keys were used is as follows. Bare with me it takes a little to go through it. 1/ A Private / Public key pair is generated for a user in the Crypto Admin application. 2/ The user is (in the crypto admin app) 'assigned' the tables that they are allowed to access 3/ For each table that the user is allowed access the users public key is used to encrypt the appropriate tables AES key, which is then stored as a table property using the users name as the property name. 4/ The users stored encrypted copy of a tables AES key can be checked for validity by either using the decrypted AES key to decypher a known value and see that it is true (such as a table property that holds a copy in encrypted form of the tables name), or by placing a MD5 hash value with the stored AES key that matches the users name or password or some other known value. I went with the latter, but the former is probably easier to do. Actually if I were to do this again, I would make a table property and store an MD5 hash of the tables name in it, encrypted with the tables AES key. When a user tries to access the table the form (code) checks to see if the user has an associated table property in their name, then uses the users private key to decrypt the stored encrypted AES key, then uses the AES key to decypt the stored MD5 hash (the known value) and checks this against the MD5 hash generated at runtime for the tables actual name. If they match then the user is valid for the table, if the MD5 hashes dont match then something has either gone wrong or someone has copied the username / stored value from another table and is using it to try and break the table in question. This way, with code, you can assign different users access to different tables without fear that because they have access to one area of data that they could access other areas that they may not be allowed to. The Crypto Admin part was to assign these users to the tables and associate the keys properly. It made life a lot easier than trying to do this through the front end, and allowed the private keys and AES keys another layer of security by never having them directly available to the 'public'. 4. *It seems obvious that any field that has been encrypted can no longer be directly bound to an Access control. *It must be displayed via a function and updated by code. This is absolutely correct. I would recommend doing the encryption control through a third application as I talked about above. Use code for everything that needs encryption, and hard code the needed functionality into the front end. Keep the admin separate from the front end, and keep the data in the back end. I hope this helps clear this up a little. As I said it was a pain to do this. The weak point as I mentioned earlier was in the storage of the usernames and passwords (with the private keys). I was giving this a little thought since your first post, and *maybe* have a better way to do it than the one I first used. It occurs to me that it would be better to keep the usernames completely obfuscated if possible so that it makes things very hard for someone to be able to reverse engineer them. For this you could use again MD5 hashing. for the users login, they would type their username and password. Both the username and password are MD5 hashed. The front end checks a table in the back end for a matching value for the username. If this is found then the password MD5 hash is used as an AES key to decrypt the users private key, and some known value check for validity same as mentioned before. As with all cryptographic applications, it is a complex task to get it right. Even the best cryptographic ciphers can be undone by poor system design (think Enigma in WW2). In this case the weakest point as I see it is the username / password area used for the login to get the users private key. If you are able to overcome that with a better system design then go for it. I would recommend it if the data is truly valuable. The best you could realistically go for here is tri- factor security, something you have (a token), something you know (username / password), and something you are (biometric). Might be overkill, but keeping the users private key out of the system would make this application really strong. If you can get to the dual factor level that would be brilliant for most purposes. Hope this helps Cheers The Frog |
#16
|
|||
|
|||
Advice on securing a sensitive Access database
In article
, " Wed, 16 Apr 2008 01:24:04 writes The AES key for each table was stored as a table property with DAO code, encrypted Asymmetrically (public / private), for each user that had access to the table. Why a table property rather than as a separate table? Just to make it not so obvious? -- Les Desser (The Reply-to address IS valid) |
#17
|
|||
|
|||
Advice on securing a sensitive Access database
In article
, The Frog Mon, 14 Apr 2008 00:45:10 writes So how do we solve the problem of your DB encryption? We use Asymmetric to encrypt the Symmetric keys. The 'heavy lifting' of encryption / decryption of the data is actually handled by the AES cipher which is relatively fast, and only the decryption of the AES keys is done with the slower Asymmetric cipher. Not sure if I quite follow that. 1. Data encrypted by AES key 2. AES key encrypted with Asymmetric public key (?) 3. AES key decrypted with Asymmetric private key (?) 4. Data decrypted by AES key What have we achieved? The Asymmetric private key still has to be made available. I'm sure your previous post has the answer to this, but I can't quite see it. -- Les Desser (The Reply-to address IS valid) |
#18
|
|||
|
|||
Advice on securing a sensitive Access database
In article
, " Wed, 16 Apr 2008 01:24:04 writes (As I worked through your notes, later parts answered some earlier questions. I have removed some but may have left some others by mistake) 1. *Relevant tables in the data mdb are individually encrypted by encrypting each relevant field. What I did here was to build a function that wasa used for both encryption and decryption of a field, The same function to do both? based on the AES algorithm. Effectively you can choose how many AES keys are used for securing the data. I used one AES key per table. A *different* key for each table? I applied the function to encrypt / decrypt such that any data read from the table was unintelligible without decrypting it through the function, and in turn no data was written to the table without being encrypted with the function. In this way each field was encrypted with the same AES key. This was done through unbound forms and code. The end user never actually saw the encryption taking place. An Access question: Could controls not be bound to the decrypt function? The AES key for each table was stored as a table property with DAO code, encrypted Asymmetrically (public / private), for each user that had access to the table. Each users public key was used to encrypt a copy of the AES key for the table and store that as the value for the property. Only the users private key would be able to decrypt the copy of the AES key stored as a property specific to that user (eg/ I made a table property with the same name as the username, and stored the encrypted (users public key) form of the AES key as the value of that property). So we have each user with their own encrypted copy of the key. 2. *Decryption keys are stored, encrypted, in the front-end db (does it have to be separate from the application front-end?) I actually kept all the encryption keys stored in the back end, and only the functionality to use them in the front end. This way the front end never really needed changing once the encrypt / decrypt functionality had been built in. Understood Everything stored in the backend db was in ciphertext (encrypted form). This way it did not matter if someone stole a copy of the backend db, it was effectively useless. If they connected via code from a different application to try and read the data all they would get is meaningless rubbish from each field. [... balance of notes left for later digestion..] 3. *Access to the decryption keys is controlled by some user entered password. The user access to each table was done via checking if the user had a table property in their name, with a stored AES key value. This was also able to be checked for validity (ie/ to see if someone had just copied the property from another table). The way the user asymmetric keys were used is as follows. Bare with me it takes a little to go through it. 1/ A Private / Public key pair is generated for a user in the Crypto Admin application. 2/ The user is (in the crypto admin app) 'assigned' the tables that they are allowed to access 3/ For each table that the user is allowed access the users public key is used to encrypt the appropriate tables AES key, which is then stored as a table property using the users name as the property name. 4/ The users stored encrypted copy of a tables AES key can be checked for validity by either using the decrypted AES key to decypher a known value and see that it is true (such as a table property that holds a copy in encrypted form of the tables name) That is OK to check one user's key being copied to an other table. What about one user's key being copied to the same table under a different user's name? Storing an encrypted copy of the table name and the user name together with the key should stop that. , or by placing a MD5 hash value with the stored AES key that matches the users name or password or some other known value. I went with the latter, but the former is probably easier to do. Actually if I were to do this again, I would make a table property and store an MD5 hash of the tables name in it, encrypted with the tables AES key. When a user tries to access the table the form (code) checks to see if the user has an associated table property in their name, then uses the users private key to decrypt the stored encrypted AES key, then uses the AES key to decypt the stored MD5 hash (the known value) and checks this against the MD5 hash generated at runtime for the tables actual name. If they match then the user is valid for the table, if the MD5 hashes dont match then something has either gone wrong or someone has copied the username / stored value from another table and is using it to try and break the table in question. Don't you also need to check in the same way in case the property has been copied on the same table for a different user? This way, with code, you can assign different users access to different tables without fear that because they have access to one area of data that they could access other areas that they may not be allowed to. Makes a lot of sense The Crypto Admin part was to assign these users to the tables and associate the keys properly. It made life a lot easier than trying to do this through the front end, and allowed the private keys and AES keys another layer of security by never having them directly available to the 'public'. 4. *It seems obvious that any field that has been encrypted can no longer be directly bound to an Access control. *It must be displayed via a function and updated by code. This is absolutely correct. I would recommend doing the encryption control through a third application as I talked about above. Use code for everything that needs encryption, and hard code the needed functionality into the front end. Keep the admin separate from the front end, and keep the data in the back end. I hope this helps clear this up a little. As I said it was a pain to do this. The weak point as I mentioned earlier was in the storage of the usernames and passwords (with the private keys). I was giving this a little thought since your first post, and *maybe* have a better way to do it than the one I first used. It occurs to me that it would be better to keep the usernames completely obfuscated if possible so that it makes things very hard for someone to be able to reverse engineer them. For this you could use again MD5 hashing. for the users login, they would type their username and password. Both the username and password are MD5 hashed. The front end checks a table in the back end for a matching value for the username. If this is found then the password MD5 hash is used as an AES key to decrypt the users private key, and some known value check for validity same as mentioned before. As with all cryptographic applications, it is a complex task to get it right. Say that again! Even the best cryptographic ciphers can be undone by poor system design (think Enigma in WW2). In this case the weakest point as I see it is the username / password area used for the login to get the users private key. If you are able to overcome that with a better system design then go for it. I wonder if some hardware would help. Fingerprint reader? (I have no idea how secure they are) I would recommend it if the data is truly valuable. The best you could realistically go for here is tri- factor security, something you have (a token), something you know (username / password), and something you are (biometric). Might be overkill, but keeping the users private key out of the system would make this application really strong. Don't understand "keeping the users private key out of the system" If you can get to the dual factor level that would be brilliant for most purposes. Hope this helps Very much so! I am at the stage that as I work through your notes I think I understand each step but I cannot say I have a clear picture in my head of all the steps. I need to re-read a few more times. -- Les Desser (The Reply-to address IS valid) |
#19
|
|||
|
|||
Advice on securing a sensitive Access database
Hi Les,
We have a lot of points here to cover, so bare with me as I attempt to work through them for you one by one:........ The AES key for each table was stored as a table property with DAO code, encrypted Asymmetrically (public / private), for each user that had access to the table. Why a table property rather than as a separate table? Just to make it not so obvious? ***Answer: The reason for this was more of a matter of design philosophy. You could also do this as a table. I preferred to make the system as difficult to duplicate as possible. That being said an experienced Access Programmer would still be able to locate the extra table properties. It just helped to rule out the 'middle' level users who know enough to be a bother but not enough to be a serious threat. The security of the data ultimately does not depend on the obfuscation of the key storage, but rather on the strength of the ciphers used. 1. Relevant tables in the data mdb are individually encrypted by encrypting each relevant field. What I did here was to build a function that wasa used for both encryption and decryption of a field, The same function to do both? *******Answer: With symmetric encryption the same function is used for both encryption and decryption purposes. You really only need one function here, but if you wish to make the code more understandable you could always create two functions, or use an object (as done in one of the implementations that yields a class module) so that you have three properties of the class, one for the AES key, and one for each the encrypted and decrypted data (plaintext and ciphertext). You could build in functionality so that if the value stored in the encrypted property was changed by placing a new value from the app to the object that the decrypted would automatically reflect the change. I chose to ditch the class module in favour of a function (If I remember correctly......). Effectively you can choose how many AES keys are used for securing the data. I used one AES key per table. A *different* key for each table? ****Answer: Yep, exactly that, a different key for each table :-) An Access question: Could controls not be bound to the decrypt function? ****Answer: It depends on your design philosophy. I chose to use code to control every aspect of each form and report. This made for a little more work in the construction of the forms, particularly with regards to list boxes and the like, but in the end the functions needed to populate the controls with appropriately decrypted data only had to be written once, and then called from the form (again by code) to actually fill the control with the decrypted data. There are a myriad of ways you could approach this, so it really is just a matter of working out one that suits the design of your app and reverse engineering it into the existing forms, reports, etc... That is OK to check one user's key being copied to an other table. What about one user's key being copied to the same table under a different user's name? Storing an encrypted copy of the table name and the user name together with the key should stop that. Don't you also need to check in the same way in case the property has been copied on the same table for a different user? ****Answer: Because of the private key / public key way of enciphering the tables AES key, only the correct private key will decrypt a users enciphered AES table key. This means that if a user duplicates another users table key and renames it for themselves (lets say), then it still wont help them unless they possess the specific private key for the table key they copied. Their own private key is unique to them and so wont work with anyone elses public key encrypted data. It only works for data that is (in this case the table key) that is encrypted with their matching public key. The key pairs are unique, and as such the protection of the private key is really important, hence also my comments on keeping the private keys out of the system if possible. I wonder if some hardware would help. Fingerprint reader? (I have no idea how secure they are) ****Answer: With regards to using hardware for storing the private keys and also for biometric authentication you need to look around at this stuff. There are a lot fo rubbish components on the market and very few that are actually reliable AND secure. One of the best that I have seen for securely storing keys was from Rainbow Technologies (this is not a product endorsement, but rather an endorsement of the type of approach used by this technology) - they had / have a product called the iKey that acted as a storage container, a very secure one, for keys / certificates, and if I remember correctly they also had a model that could do cryptographic calculations. It was basically a USB key, so no special hardware was needed on the computer with the application, just some drivers. I saw this about ten years ago now, so I would expect that today there are many versions of this sort of thing available. If you can find one that has a simple to administer system for creating / removing users and keys, and can be integrated into your app without too much hassle then I would certainly entertain the idea. It was what I wanted to do with the app I built. I was hoping to find a USB key, with the ability to store the Public key for a user as well as have it password protected on the key, and with an integrated fingerprint/thimbprint reader. This would have been a nice tri-factor authentication system. Cost is also a factor here too, some of these things can be pretty expensive from memory. I have also seen keyboards that have integrated smart card readers (credit card tpe cards with smart chips on them) and finger / thumbprint readers. I have also heard of software that can tell who you are by just the way you type on the keyboard. Voice is another possibility, but of course it can be recorded. A passing thought on security here too. If your data is **REALLY** sensitive, and the possibility exists that a user may be co-erced by force into accessing the system / information, you may want to think about placing a dummy table with false data that LOOKS real enough. What you do is to have the user enter their password backwards or something like that when under duress, and on login check it, and if the password is entered backwards then only show the rubbish fake data - but make the application look like it is working perfectly. At the same time send a message to someone to let them know of the intrusion and duress situation. Pretty extreme and certainly not an everyday thing, but I have seen situations where this is necessary. Don't understand "keeping the users private key out of the system" ****Answer: I think we covered this above, but again briefly it comes down to a matter of adequately securing the system. The private / public key pairs are the core of getting access to the tables AES keys. What we need to do to really make it safe is to keep the private key as secure as possible. Because the private keys, in the model I ended up deploying, are stored in the database itself (although encrypted), they represent a risk to the security of the system. Even though the encryption is strong (AES 256), the password that the user chooses becomes the weak point - it could be guessed or forced from the user - and hence the private key becomes available - hence the data becomes available. By keeping the private key separate completely, it does not matter if the user password is guessable or not - you are just eliminating risk from the system design. If you use longer passwords as a minimum then users tend to write them down or use easily rememberable phrases that can be guessed. This goes back to the point about tri-factor authentication. That is considered by many to be very strong, but even dual factor would provide a massive increase in the level of security for the application - in this case username/password (something you know) and a separate device/ storage for the private key (something you have). The 'something you are' part would put the icing on the cake so to speak. So in short, storing the private keys is the weakest point in my existing app, due to the fact that a users password may be obtainable. Keeping the private keys out of the system goes a long way to eliminating this deficiency, and having a biometric pretty well completes it. Another possibility would be to require two users to authenticate themselves before the system was functional, and there are cryptographic methods that can achieve this. Its all a matter of approach. How far do you want to go to protect the data? What is the cost of having unauthorised access? Basically you need a risk analysis to figure out how far to go. There is always another level of security you can add, the trick is knowing how little is too little and how much is too much. What have we achieved? The Asymmetric private key still has to be made available. (from the third posting) ****Answer: That is exactly correct, the private key for the user needs to be made available to the user when they perform a successful login. Only then should the users private key be available to them and the application. The public key can be seen by anyone so it doesnt really matter. The users private key is NEEDED to obtain and decrypt the tables AES key. Thats why we have a copy of the AES key encrypted with table for each user, using each users unique public key. I think what you missed here was that each user gets a unique public / private assymmetric key pair. Only the AES key is common, and only then on a per table basis. Its kind of like having a lock box for each user, and each lock box has a unique key that only that user has. Inside each lock box is another key, lets say to the beer fridge :-). If I take my lock box, using my key and open it, then I can get access to the beer fridge key and hence the beer. If however I take my key, and I try and use it to open another users lock box it wont work because it needs that users key and not mine - so I cant get the berr fridge key and hence no beer :-( What we have in this design is the same beer fridge thinking for each table, in effect a series of independantly locked beer fridges - the key from one beer fridge wont open another fridge. We have for each fridge a set of lock boxes, one for each user, each secured with the users lock (same lock for one user across all of their lock boxes). The user can take their key, open their lock box if they have one for a specific fridge, then take the key to the fridge and get some beer. The user cant open another users lock box, and they cant take a key from one fridge and use it in another fridge. BUT, because the user has only a single key for all of their lock boxes (and hence all the fridges that they can get beer from because they can get the fridge key), it is very important to protect the users private key. That private key in this analogy is the unique user private key they obtain when they log in, and in my app is stored in the back end database - and hence also my strong desire to keep the key away from the database and potentially weak passwords. I hope this helps a bit. I know this can be a tricky area to deal with. As I said before, the application of the cryptography is the key to success here. Its worth taking a little extra time to get the details worked out for the implementation. The algorithms are actually only useful if they are applied in a secure system design. The most common cryptographic mistake I have seen is people using really capable algorithms and really poor system design - effectivley putting a steel door on the front of the home and a fly wire screen on the back. If you can get the private keys out of the application / database and store them separately and securely then do it! Cheers The Frog |
#20
|
|||
|
|||
Advice on securing a sensitive Access database
In article
, " Wed, 16 Apr 2008 01:24:04 writes Needless to say this was a time consuming process but a nice safety feature to have. I know this is really an "impossible" question to answer accurately, but .... What sort of effort - in man days - should this project take in Access for an experienced Access developer (ignoring research on the cryptology side). -- Les Desser (The Reply-to address IS valid) |
Thread Tools | |
Display Modes | |
|
|