How to derive ethereum address from Elliptic Curve secp256k1 - SHA256 Digest - cryptography

I am using google cloud key management service to generate and manage keys. I have generated the HSM key for Asymmetric signing using Elliptic Curve secp256k1 - SHA256 Digest. The public key is something as below -
{
pem: '-----BEGIN PUBLIC KEY-----\n' +
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n' +
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n' +
'-----END PUBLIC KEY-----\n',
algorithm: 'EC_SIGN_SECP256K1_SHA256',
pemCrc32c: { value: '12345678' },
name: 'PATH-TO-KEY-ON-KMS/cryptoKeyVersions/1',
protectionLevel: 'HSM'
}
I am looking to derive Ethereum address from this so that I can fund the wallet and perform signing. For the same I have written a function as below -
const deriveEthAddress = async () => {
const publicKey = await getPublicKey(); // this returns same key as show above snippet
const address = keccak256(publicKey.pem);
const hexAddress = address.toString('hex');
return '0x' + hexAddress.toString('hex').substring(hexAddress.length - 40, hexAddress.length)
}
This function gives me ethereum checksum verified address, but not sure is it the correct way to do this. Is this solution correct or needs improvement?
Example public key:
publicKey {
pem: '-----BEGIN PUBLIC KEY-----\n' +
'MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEeYRv0S7Zf5CNh5/APxiT6xoY+z521DHT\n' +
'FgLdUPUP2e/3jkYDuZTbCHP8zEHm7nhG6AUOpJCbTF2J2vWkC1i3Yg==\n' +
'-----END PUBLIC KEY-----\n',
algorithm: 'EC_SIGN_SECP256K1_SHA256',
pemCrc32c: { value: '41325621' },
name: 'PATH-TO-KEY-ON-KMS/cryptoKeyVersions/1',
protectionLevel: 'HSM'
}
And, ethereum address I am deriving is - 0x8aCd56527DfE9205edf7D6F1EB39A5c9aa8aaE3F

You must not use the PEM encoded public key when determining the Keccak hash, but must apply the raw public key, i.e. the concatenation of the hex encoded x and y value.
This can be derived most easily from the PEM encoded key by converting it to a DER encoded key. The last 64 bytes of the DER encoded key correspond to the raw public key (at least for secp256k1, the elliptic curve used by Ethereum):
var publicKey = {
pem: '-----BEGIN PUBLIC KEY-----\n' +
...
}
// Export raw public key (without 0x04 prefix)
var x509pem = publicKey.pem;
var x509der = crypto.createPublicKey(x509pem).export({format: 'der', type: 'spki'});
var rawXY = x509der.subarray(-64);
console.log('Raw key: 0x' + rawXY.toString('hex')); // 79846fd12ed97f908d879fc03f1893eb1a18fb3e76d431d31602dd50f50fd9eff78e4603b994db0873fccc41e6ee7846e8050ea4909b4c5d89daf5a40b58b762
For your pem encoded public key the raw public key is (hex encoded):
79846fd12ed97f908d879fc03f1893eb1a18fb3e76d431d31602dd50f50fd9eff78e4603b994db0873fccc41e6ee7846e8050ea4909b4c5d89daf5a40b58b762
The Ethereum address derived from this is (hex encoded):
fd55ad0678e9b90d5f5175d7ce5fd1ebd440309d
or with checksum:
Fd55aD0678E9b90D5f5175d7cE5fD1eBd440309D
which can be verified on https://www.rfctools.com/ethereum-address-test-tool.
Full code:
const crypto = require('crypto');
const keccak256 = require('keccak256');
var publicKey = {
pem: '-----BEGIN PUBLIC KEY-----\n' +
'MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEeYRv0S7Zf5CNh5/APxiT6xoY+z521DHT\n' +
'FgLdUPUP2e/3jkYDuZTbCHP8zEHm7nhG6AUOpJCbTF2J2vWkC1i3Yg==\n' +
'-----END PUBLIC KEY-----\n',
algorithm: 'EC_SIGN_SECP256K1_SHA256',
pemCrc32c: { value: '41325621' },
name: 'PATH-TO-KEY-ON-KMS/cryptoKeyVersions/1',
protectionLevel: 'HSM'
}
// Export raw public key (without 0x04 prefix)
var x509pem = publicKey.pem;
var x509der = crypto.createPublicKey(x509pem).export({format: 'der', type: 'spki'});
var rawXY = x509der.subarray(-64);
console.log('Raw key: 0x' + rawXY.toString('hex')); // 79846fd12ed97f908d879fc03f1893eb1a18fb3e76d431d31602dd50f50fd9eff78e4603b994db0873fccc41e6ee7846e8050ea4909b4c5d89daf5a40b58b762
// Derive address from raw public key
var hashXY = keccak256(rawXY);
var address = hashXY.subarray(-20).toString('hex').toLowerCase();
// Calculate checksum (expressed as upper/lower case in the address)
var addressHash = keccak256(address).toString('hex');
var addressChecksum = '';
for (var i = 0; i < address.length; i++){
if (parseInt(addressHash[i], 16) > 7) {
addressChecksum += address[i].toUpperCase();
} else {
addressChecksum += address[i];
}
}
console.log('Derived: 0x' + addressChecksum); // 0xFd55aD0678E9b90D5f5175d7cE5fD1eBd440309D
console.log('Test: 0xFd55aD0678E9b90D5f5175d7cE5fD1eBd440309D'); // from https://www.rfctools.com/ethereum-address-test-tool using the raw key

Related

how to get correct address using ecrecover in tron

The code block of getting signer address via ecrecover in solidity contract code is working well with Ethereum but in TRON it is returning wrong address
My contract side code is
function validate(string memory strTest,uint8 v, bytes32 r, bytes32 s) public view returns(address){
bytes32 prefixedHash = keccak256(strTest);
return ecrecover(keccak256(prefixedHash),v,r,s);
// return ecrecover(prefixedHash,v,r,s):
}
and dapp side code is
msg = tronWeb.sha3("this is test string");
var signature = await tronWeb.trx.sign(msg);
var r=signature.substr(0,66);
var s= "0x" + signature.substr(66,64);
var v="0x" + signature.substr(signature.length-2);
retValue = await thisContractInstance.validate("this is test string",v,r,s).call();
but in both cases ( one case is commented in contract side code) getting wrong signer address in TRON shasta network
Are you trying to sign a string or sign transaction? #ashwin
I had the same kind of issues signing a string, and the below helped me out.
Tronweb uses different methods for signing transactions and signing strings.
1) For transactions you can refer this: https://github.com/TRON-US/tronweb/blob/master/src/lib/trx.js#L651,
2) Regarding signing string, it uses
ethers.utils.SigningKey.signDigest ()
of Ethereum: https://docs.ethers.io/ethers.js/html/api-advanced.html?highlight=signingkey#id8, note that the final v byte will always be nomalized for Solidity (either 27 or 28)
If you are trying to sign the msg as mentioned here,
var signature = await tronWeb.trx.sign(msg);
you can follow the second.
You could refer the article https://medium.com/#coredevs/verifying-elliptic-curve-digital-signature-with-tron-smart-contract-5d11347e7b5b.
This will be useful to accomplish signing the strings,
along with the above answer provided by #Aldo.
Happy coding.
Taken from here:
The smart contract code:
contract Verifier {
function recoverAddr(bytes32 msgHash, uint8 v, bytes32 r, bytes32 s) returns (address) {
return ecrecover(msgHash, v, r, s);
}
function isSigned(address _addr, bytes32 msgHash, uint8 v, bytes32 r, bytes32 s) returns (bool) {
return ecrecover(msgHash, v, r, s) == _addr;
}
}
The client code:
const ethers = tronWeb.utils.ethersUtils;
let contract = await tronWeb.contract().at(contract_address);
let signingKey = new ethers.SigningKey(tronWeb.defaultPrivateKey);
let message = "This is some message";
let messageBytes = ethers.toUtf8Bytes(message);
let messageDigest = ethers.keccak256(messageBytes);
let signature = signingKey.signDigest(messageDigest);
let hexAddress = await contract.recoverAddr(messageDigest, signature.v, signature.r, signature.s).call();
According to https://github.com/tronprotocol/tronweb/blob/1435436f83b910f19e9205998e348ea093732ce5/src/lib/trx.js#L629
tronWeb.trx.sign() does the following
Pass in 32 bytes of hash
The prefix "\x19TRON Signed Message:\n32" needs to be added first
Generate a new hash with the keccak256 algorithm
Signature (TronLink or private key)
Therefore, when using solidity's ecrecover, the hash of step 3 is passed in.
function getHash(hash) {
hash = hash.replace(/^0x/, '')
if (!tronWeb.utils.isHex(hash)) {
return
}
if (hash.length !== 64) {
return
}
const {
keccak256,
toUtf8Bytes
} = tronWeb.utils.ethersUtils
const hashArray = tronWeb.utils.code.hexStr2byteArray(hash)
const TRX_MESSAGE_PREFIX = toUtf8Bytes('\x19TRON Signed Message:\n32')
return keccak256([
...TRX_MESSAGE_PREFIX,
...hashArray
])
}

Jackson2HashMapper not serializing Date with Flatten=True

Using the Jackson Hash Mapper with Flatten=true my Date fields are getting discarded. Is this the correct behaviour or a bug? Is there a way to have Date serialized with Flatten=true?
I've used the following test Pojo:
import java.util.Date;
public class FooClass{
private Boolean foolean;
private Integer barteger;
private String simpleString;
private Date myDate;
public void setFoolean(Boolean value){ foolean = value; }
public Boolean getFoolean(){ return foolean; }
public void setBarteger(Integer value){ barteger = value; }
public Integer getBarteger(){ return barteger; }
public void setSimpleString(String value) { simpleString = value; }
public String getSimpleString(){ return simpleString; }
public void setMyDate(Date value) { myDate = value; }
public Date getMyDate(){ return myDate; }
}
public class Main {
public static void main(String[] args) throws ParseException,
JsonParseException, JsonMappingException, IOException {
Jackson2HashMapper hashMapper = new Jackson2HashMapper(true);
FooClass fooObject = new FooClass();
fooObject.setFoolean(true);
fooObject.setBarteger(10);
fooObject.setSimpleString("Foobar");
fooObject.setMyDate(new Date());
Map<String, Object> hash = hashMapper.toHash(fooObject);
for (String key: hash.keySet())
{
System.out.println("hash contains: " + key + "=" +
hash.get(key.toString()));
}
FooClass newFoo = (FooClass)(hashMapper.fromHash(hash));
System.out.println("FromHash: " + newFoo);
}
}
In this case I get the following output:
hash contains: #class=FooClass
hash contains: foolean=true
hash contains: barteger=10
hash contains: simpleString=Foobar
FromHash: FooClass#117159c0
If I change new Jackson2HashMapper(false); then I get:
hash contains: #class=FooClass
hash contains: foolean=true
hash contains: barteger=10
hash contains: simpleString=Foobar
hash contains: myDate=[java.util.Date, 1547033077869]
FromHash: FooClass#7ed7259e
I was expecting to get the Date field serialized in both cases - perhaps with an additional field describing the date type (flattened).
I traced the reason for this to the following line in the HashMapper code:
typingMapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);
Where the mapper is configured.
It seems to issue in Jackson2HashMapper.
After digging into the source of Jackson2HashMapper, it seems to issue in Jackson2HashMapper.
created an issue for this, DATAREDIS-1001
Jackson2HashMapper does not serialize Date/Calender fields when flatten = true

How to migrate SimpleMembership user data to ASPNET Core Identity

I have an application that I originally built when I was young and foolish and its authentication is set up using the SimpleMembership framework, with all user data contained in a webpages_Membership table. I am very interested in rebuilding my backend as an AspNetCore Web API with AspNetCore Identity via SQL Server, without losing user information.
I've had good luck with coming up with SQL scripts to move everything into an AspNetUsers table in preparation for working with Identity instead of SimpleMembership, but where I'm running into an issue is password hashing. I gather from articles like this and this that my best bet is to override PasswordHasher<IdentityUser> to duplicate the SimpleMembership crypto flow, and then rehash passwords as they come in to gradually migrate the database.
The trouble is I can't find out how to achieve this flow duplication in .NET Core. The latter article linked above states that the SimpleMembership flow is achieved via the System.Web.Helpers.Crypto package, which does not appear to exist in the .NET Core library, and I can't figure out if its implementation is documented anywhere. (Its MSDN documentation says that it is using RFC2898 hashing but I don't know enough about crypto to know if that's enough to go on by itself. This isn't my area of expertise. :( )
Any insight on how to approach this would be much appreciated. Thank you!
For anyone else who may be running into the same trouble -- I was able to find a copy of the System.Web.Helpers.Crypto code somewhere on GitHub, and more or less copied it into a custom password hasher class thus:
public class CustomPasswordHasher : PasswordHasher<IdentityUser>
{
public override PasswordVerificationResult VerifyHashedPassword(IdentityUser user, string hashedPassword,
string providedPassword)
{
var isValidPasswordWithLegacyHash = VerifyHashedPassword(hashedPassword, providedPassword);
return isValidPasswordWithLegacyHash
? PasswordVerificationResult.SuccessRehashNeeded
: base.VerifyHashedPassword(user, hashedPassword, providedPassword);
}
private const int _pbkdf2IterCount = 1000;
private const int _pbkdf2SubkeyLength = 256 / 8;
private const int _saltSize = 128 / 8;
public static bool VerifyHashedPassword(string hashedPassword, string password)
{
//Checks password using legacy hashing from System.Web.Helpers.Crypto
var hashedPasswordBytes = Convert.FromBase64String(hashedPassword);
if (hashedPasswordBytes.Length != (1 + _saltSize + _pbkdf2SubkeyLength) || hashedPasswordBytes[0] != 0x00)
{
return false;
}
var salt = new byte[_saltSize];
Buffer.BlockCopy(hashedPasswordBytes, 1, salt, 0, _saltSize);
var storedSubkey = new byte[_pbkdf2SubkeyLength];
Buffer.BlockCopy(hashedPasswordBytes, 1 + _saltSize, storedSubkey, 0, _pbkdf2SubkeyLength);
byte[] generatedSubkey;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, _pbkdf2IterCount))
{
generatedSubkey = deriveBytes.GetBytes(_pbkdf2SubkeyLength);
}
return ByteArraysEqual(storedSubkey, generatedSubkey);
}
internal static string BinaryToHex(byte[] data)
{
var hex = new char[data.Length * 2];
for (var iter = 0; iter < data.Length; iter++)
{
var hexChar = (byte) (data[iter] >> 4);
hex[iter * 2] = (char) (hexChar > 9 ? hexChar + 0x37 : hexChar + 0x30);
hexChar = (byte) (data[iter] & 0xF);
hex[iter * 2 + 1] = (char) (hexChar > 9 ? hexChar + 0x37 : hexChar + 0x30);
}
return new string(hex);
}
[MethodImpl(MethodImplOptions.NoOptimization)]
private static bool ByteArraysEqual(byte[] a, byte[] b)
{
if (ReferenceEquals(a, b))
{
return true;
}
if (a == null || b == null || a.Length != b.Length)
{
return false;
}
var areSame = true;
for (var i = 0; i < a.Length; i++)
{
areSame &= (a[i] == b[i]);
}
return areSame;
}
}
This class overrides VerifyHashedPassword and checks whether the user's provided password works with the old Crypto hashing; if so, the method returns PasswordVerificationResult.SuccessRehashNeeded. Otherwise, it passes the password off to the base class's method and verifies it as normal with the .NET Core hashing behavior.
You can then instruct your UserManager to use this password hasher instead of the default by including it in your dependency injection configuration in Startup.cs:
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IPasswordHasher<IdentityUser>, CustomPasswordHasher>();
}
...
}
My eventual intention is to have my controller trigger a rehash of the user's password when that SuccessRehashNeeded result is returned, allowing a gradual migration of all users to the correct hashing schema.

How to decrypt hash security in phalcon

Save crypt hash security:
$usuario = new User();
$usuario->password = $this->security->hash($this->request->getPost("pass"));
if (!$usuario->save()) {
foreach ($usuario->getMessages() as $message) {
$this->flash->error($message);
}
$this->dispatcher->forward([
'controller' => "usuario",
'action' => 'new'
]);
return;
}
now, How to decrypt hash security to send my form:
$usuario = User::findFirstByid($iduser);
$this->tag->setDefault("pass", $this->encryption->decrypt($usuario->password));
I having this: Notice: Access to undefined property encryption in ...
As #Juri said Hashes are only one way. You can however use encryptBase64() which is two way and you can decode it back. Do not use it for passwords, use it for some data you need to pass to someone and read it back, like api token etc..
Here is how to set it up:
$di->setShared('crypt', function () use ($config) {
$crypt = new \Phalcon\Crypt();
$crypt->setKey('i$4^&/:%2#k50ROQ<#{(e=*!<7u|rI~0');
return $crypt;
});
Helper functions I created, but you can use it directly:
...
...
private static $cryptKey = 'i$4^&/:%2#k50ROQ<#{(e=*!<7u|rI~0';
/**
* Generate url safe encoded string
*
* #param string $string string to be encoded
* #return encoded string
*/
public static function encodeString($string)
{
$crypt = new \Phalcon\Crypt();
return $crypt->encryptBase64($string, self::$cryptKey, true);
}
/**
* Decode string generated with Common::encodeString()
*
* #param string $string Encoded string
* #return decoded string
*/
public static function decodeString($string)
{
$crypt = new \Phalcon\Crypt();
return $crypt->decryptBase64($string, self::$cryptKey, true);
}

How Do I Avoid SSL Certificates during Development for a WCF endpoint that will be secured during Production

We are developing a number of WCF services. requests will cross a domain boundry; that is, the clients are running in one domain and the servers handling the requests are in a different (production) domain. I know how to secure this link with SSL and certificates. We will aks the users for their usernames and passwords on the production domain and pass those in the SOAP headers.
My problem is what to do during development and "beta" testing. I know that I can get a temporary certificate and use that during development. I am wondering what my alternatives to this approach are. What have others done in this situation?
Update: I am not sure that I have gotten a "good" answer to my question. I am part of a large team (50+) of developers. The organization is fairly agile. Any one of the developers could end up working on the project that is using WCF. In fact several of the other projects are doing something similar but for different web sites and services. What I was looking for was a way that I could have anyone come in and work on this particular project for a few days without having to jump through a number of hoops. Installing the development certificate is one of those hoops. I fully understand that "dogfooding" the WCF structure during development is the best practice. Most the answers gave that as the answer. I wanted to know what, if anything, made sense that was other than "get a test certificate (or two) and install it on all of the developer boxes."
Jon
UPDATE: We actually use the much simpler Keith Brown solution instead of this now, see the source code he has provided. Advantage: No unmanaged code to maintain.
If you still want to see how to do it using C/C++, read on...
Only recommended for use in Development of course, not production, but there is a low-friction way to generate X.509 certificates (without resorting to makecert.exe).
If you have access to CryptoAPI on Windows, the idea is that you use CryptoAPI calls to generate RSA public and private keys, sign and encode a new X.509 certificate, put it in a memory-only certificate store, and then use PFXExportCertStore() to generate the .pfx bytes which you can then pass to X509Certificate2 constructor.
Once you have an X509Certificate2 instance, you can set it as a property of the appropriate WCF objects, and things just start working.
I have some example code I've written, no guarantees of any sort of course, and you'll need a bit of C experience to write the bits that have to be unmanaged (it would be a lot more painful to write the P/Invoke for all CryptoAPI calls than to have that part be in C/C++).
Example C# code that uses the unmanaged helper function:
public X509Certificate2 GenerateSelfSignedCertificate(string issuerCommonName, string keyPassword)
{
int pfxSize = -1;
IntPtr pfxBufferPtr = IntPtr.Zero;
IntPtr errorMessagePtr = IntPtr.Zero;
try
{
if (!X509GenerateSelfSignedCertificate(KeyContainerName, issuerCommonName, keyPassword, ref pfxSize, ref pfxBufferPtr, ref errorMessagePtr))
{
string errorMessage = null;
if (errorMessagePtr != IntPtr.Zero)
{
errorMessage = Marshal.PtrToStringUni(errorMessagePtr);
}
throw new ApplicationException(string.Format("Failed to generate X.509 server certificate. {0}", errorMessage ?? "Unspecified error."));
}
if (pfxBufferPtr == IntPtr.Zero)
{
throw new ApplicationException("Failed to generate X.509 server certificate. PFX buffer not initialized.");
}
if (pfxSize <= 0)
{
throw new ApplicationException("Failed to generate X.509 server certificate. PFX buffer size invalid.");
}
byte[] pfxBuffer = new byte[pfxSize];
Marshal.Copy(pfxBufferPtr, pfxBuffer, 0, pfxSize);
return new X509Certificate2(pfxBuffer, keyPassword);
}
finally
{
if (pfxBufferPtr != IntPtr.Zero)
{
Marshal.FreeHGlobal(pfxBufferPtr);
}
if (errorMessagePtr != IntPtr.Zero)
{
Marshal.FreeHGlobal(errorMessagePtr);
}
}
}
The X509GenerateSelfSignedCertificate function implementation could go something like this (you need WinCrypt.h):
BOOL X509GenerateSelfSignedCertificate(LPCTSTR keyContainerName, LPCTSTR issuerCommonName, LPCTSTR keyPassword, DWORD *pfxSize, BYTE **pfxBuffer, LPTSTR *errorMessage)
{
// Constants
#define CERT_DN_ATTR_COUNT 1
#define SIZE_SERIALNUMBER 8
#define EXPIRY_YEARS_FROM_NOW 2
#define MAX_COMMON_NAME 8192
#define MAX_PFX_SIZE 65535
// Declarations
HCRYPTPROV hProv = NULL;
BOOL result = FALSE;
// Sanity
if (pfxSize != NULL)
{
*pfxSize = -1;
}
if (pfxBuffer != NULL)
{
*pfxBuffer = NULL;
}
if (errorMessage != NULL)
{
*errorMessage = NULL;
}
if (keyContainerName == NULL || _tcslen(issuerCommonName) <= 0)
{
SetOutputErrorMessage(errorMessage, _T("Key container name must not be NULL or an empty string."));
return FALSE;
}
if (issuerCommonName == NULL || _tcslen(issuerCommonName) <= 0)
{
SetOutputErrorMessage(errorMessage, _T("Issuer common name must not be NULL or an empty string."));
return FALSE;
}
if (keyPassword == NULL || _tcslen(keyPassword) <= 0)
{
SetOutputErrorMessage(errorMessage, _T("Key password must not be NULL or an empty string."));
return FALSE;
}
// Start generating
USES_CONVERSION;
if (CryptAcquireContext(&hProv, keyContainerName, MS_DEF_RSA_SCHANNEL_PROV, PROV_RSA_SCHANNEL, CRYPT_MACHINE_KEYSET) ||
CryptAcquireContext(&hProv, keyContainerName, MS_DEF_RSA_SCHANNEL_PROV, PROV_RSA_SCHANNEL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET))
{
HCRYPTKEY hKey = NULL;
// Generate 1024-bit RSA keypair.
if (CryptGenKey(hProv, AT_KEYEXCHANGE, CRYPT_EXPORTABLE | RSA1024BIT_KEY, &hKey))
{
DWORD pkSize = 0;
PCERT_PUBLIC_KEY_INFO pkInfo = NULL;
// Export public key for use by certificate signing.
if (CryptExportPublicKeyInfo(hProv, AT_KEYEXCHANGE, X509_ASN_ENCODING, NULL, &pkSize) &&
(pkInfo = (PCERT_PUBLIC_KEY_INFO)LocalAlloc(0, pkSize)) &&
CryptExportPublicKeyInfo(hProv, AT_KEYEXCHANGE, X509_ASN_ENCODING, pkInfo, &pkSize))
{
CERT_RDN_ATTR certDNAttrs[CERT_DN_ATTR_COUNT];
CERT_RDN certDN[CERT_DN_ATTR_COUNT] = {{1, &certDNAttrs[0]}};
CERT_NAME_INFO certNameInfo = {CERT_DN_ATTR_COUNT, &certDN[0]};
DWORD certNameSize = -1;
BYTE *certNameData = NULL;
certDNAttrs[0].dwValueType = CERT_RDN_UNICODE_STRING;
certDNAttrs[0].pszObjId = szOID_COMMON_NAME;
certDNAttrs[0].Value.cbData = (DWORD)(_tcslen(issuerCommonName) * sizeof(WCHAR));
certDNAttrs[0].Value.pbData = (BYTE*)T2W((LPTSTR)issuerCommonName);
// Encode issuer name into certificate name blob.
if (CryptEncodeObject(X509_ASN_ENCODING, X509_NAME, &certNameInfo, NULL, &certNameSize) &&
(certNameData = (BYTE*)LocalAlloc(0, certNameSize)) &&
CryptEncodeObject(X509_ASN_ENCODING, X509_NAME, &certNameInfo, certNameData, &certNameSize))
{
CERT_NAME_BLOB issuerName;
CERT_INFO certInfo;
SYSTEMTIME systemTime;
FILETIME notBefore;
FILETIME notAfter;
BYTE serialNumber[SIZE_SERIALNUMBER];
DWORD certSize = -1;
BYTE *certData = NULL;
issuerName.cbData = certNameSize;
issuerName.pbData = certNameData;
// Certificate should be valid for a decent window of time.
ZeroMemory(&certInfo, sizeof(certInfo));
GetSystemTime(&systemTime);
systemTime.wYear -= 1;
SystemTimeToFileTime(&systemTime, &notBefore);
systemTime.wYear += EXPIRY_YEARS_FROM_NOW;
SystemTimeToFileTime(&systemTime, &notAfter);
// Generate a throwaway serial number.
if (CryptGenRandom(hProv, SIZE_SERIALNUMBER, serialNumber))
{
certInfo.dwVersion = CERT_V3;
certInfo.SerialNumber.cbData = SIZE_SERIALNUMBER;
certInfo.SerialNumber.pbData = serialNumber;
certInfo.SignatureAlgorithm.pszObjId = szOID_RSA_MD5RSA;
certInfo.Issuer = issuerName;
certInfo.NotBefore = notBefore;
certInfo.NotAfter = notAfter;
certInfo.Subject = issuerName;
certInfo.SubjectPublicKeyInfo = *pkInfo;
// Now sign and encode it.
if (CryptSignAndEncodeCertificate(hProv, AT_KEYEXCHANGE, X509_ASN_ENCODING, X509_CERT_TO_BE_SIGNED, (LPVOID)&certInfo, &(certInfo.SignatureAlgorithm), NULL, NULL, &certSize) &&
(certData = (BYTE*)LocalAlloc(0, certSize)) &&
CryptSignAndEncodeCertificate(hProv, AT_KEYEXCHANGE, X509_ASN_ENCODING, X509_CERT_TO_BE_SIGNED, (LPVOID)&certInfo, &(certInfo.SignatureAlgorithm), NULL, certData, &certSize))
{
HCERTSTORE hCertStore = NULL;
// Open a new temporary store.
if ((hCertStore = CertOpenStore(CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, CERT_STORE_CREATE_NEW_FLAG, NULL)))
{
PCCERT_CONTEXT certContext = NULL;
// Add to temporary store so we can use the PFX functions to export a store + private keys in PFX format.
if (CertAddEncodedCertificateToStore(hCertStore, X509_ASN_ENCODING, certData, certSize, CERT_STORE_ADD_NEW, &certContext))
{
CRYPT_KEY_PROV_INFO keyProviderInfo;
// Link keypair to certificate (without this the keypair gets "lost" on export).
ZeroMemory(&keyProviderInfo, sizeof(keyProviderInfo));
keyProviderInfo.pwszContainerName = T2W((LPTSTR)keyContainerName);
keyProviderInfo.pwszProvName = MS_DEF_RSA_SCHANNEL_PROV_W; /* _W used intentionally. struct hardcodes LPWSTR. */
keyProviderInfo.dwProvType = PROV_RSA_SCHANNEL;
keyProviderInfo.dwFlags = CRYPT_MACHINE_KEYSET;
keyProviderInfo.dwKeySpec = AT_KEYEXCHANGE;
// Finally, export to PFX and provide to caller.
if (CertSetCertificateContextProperty(certContext, CERT_KEY_PROV_INFO_PROP_ID, 0, (LPVOID)&keyProviderInfo))
{
CRYPT_DATA_BLOB pfxBlob;
DWORD pfxExportFlags = EXPORT_PRIVATE_KEYS | REPORT_NO_PRIVATE_KEY | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY;
// Calculate size required.
ZeroMemory(&pfxBlob, sizeof(pfxBlob));
if (PFXExportCertStore(hCertStore, &pfxBlob, T2CW(keyPassword), pfxExportFlags))
{
pfxBlob.pbData = (BYTE *)LocalAlloc(0, pfxBlob.cbData);
if (pfxBlob.pbData != NULL)
{
// Now export.
if (PFXExportCertStore(hCertStore, &pfxBlob, T2CW(keyPassword), pfxExportFlags))
{
if (pfxSize != NULL)
{
*pfxSize = pfxBlob.cbData;
}
if (pfxBuffer != NULL)
{
// Caller must free this.
*pfxBuffer = pfxBlob.pbData;
}
else
{
// Caller did not provide target pointer to receive buffer, free ourselves.
LocalFree(pfxBlob.pbData);
}
result = TRUE;
}
else
{
SetOutputErrorMessage(errorMessage, _T("Failed to export certificate in PFX format (0x%08x)."), GetLastError());
}
}
else
{
SetOutputErrorMessage(errorMessage, _T("Failed to export certificate in PFX format, buffer allocation failure (0x%08x)."), GetLastError());
}
}
else
{
SetOutputErrorMessage(errorMessage, _T("Failed to export certificate in PFX format, failed to calculate buffer size (0x%08x)."), GetLastError());
}
}
else
{
SetOutputErrorMessage(errorMessage, _T("Failed to set certificate key context property (0x%08x)."), GetLastError());
}
}
else
{
SetOutputErrorMessage(errorMessage, _T("Failed to add certificate to temporary certificate store (0x%08x)."), GetLastError());
}
CertCloseStore(hCertStore, 0);
hCertStore = NULL;
}
else
{
SetOutputErrorMessage(errorMessage, _T("Failed to create temporary certificate store (0x%08x)."), GetLastError());
}
}
else
{
SetOutputErrorMessage(errorMessage, _T("Failed to sign/encode certificate or out of memory (0x%08x)."), GetLastError());
}
if (certData != NULL)
{
LocalFree(certData);
certData = NULL;
}
}
else
{
SetOutputErrorMessage(errorMessage, _T("Failed to generate certificate serial number (0x%08x)."), GetLastError());
}
}
else
{
SetOutputErrorMessage(errorMessage, _T("Failed to encode X.509 certificate name into ASN.1 or out of memory (0x%08x)."), GetLastError());
}
if (certNameData != NULL)
{
LocalFree(certNameData);
certNameData = NULL;
}
}
else
{
SetOutputErrorMessage(errorMessage, _T("Failed to export public key blob or out of memory (0x%08x)."), GetLastError());
}
if (pkInfo != NULL)
{
LocalFree(pkInfo);
pkInfo = NULL;
}
CryptDestroyKey(hKey);
hKey = NULL;
}
else
{
SetOutputErrorMessage(errorMessage, _T("Failed to generate public/private keypair for certificate (0x%08x)."), GetLastError());
}
CryptReleaseContext(hProv, 0);
hProv = NULL;
}
else
{
SetOutputErrorMessage(errorMessage, _T("Failed to acquire cryptographic context (0x%08x)."), GetLastError());
}
return result;
}
void
SetOutputErrorMessage(LPTSTR *errorMessage, LPCTSTR formatString, ...)
{
#define MAX_ERROR_MESSAGE 1024
va_list va;
if (errorMessage != NULL)
{
size_t sizeInBytes = (MAX_ERROR_MESSAGE * sizeof(TCHAR)) + 1;
LPTSTR message = (LPTSTR)LocalAlloc(0, sizeInBytes);
va_start(va, formatString);
ZeroMemory(message, sizeInBytes);
if (_vstprintf_s(message, MAX_ERROR_MESSAGE, formatString, va) == -1)
{
ZeroMemory(message, sizeInBytes);
_tcscpy_s(message, MAX_ERROR_MESSAGE, _T("Failed to build error message"));
}
*errorMessage = message;
va_end(va);
}
}
We've used this to generate SSL certificates on startup, which is fine when you only want to test encryption and not verify trust/identity, and it takes only about 2-3 seconds to generate.
Really you want your development environment to match production as much as possible. WCF will check revocation lists during transport negotiation or signature checking and self signed certificates, or faked certifications using makecert do not support CRLs.
If you have a spare machine you could use Windows Certificate Services (free with Server 2003 and 2008). This provides a CA and you can request certificates (SSL or client) from it. It needs to be a spare machine as it sets itself up under the default web site and completely messes up if you have already tweaked that. It also publishes CRLs. All you would need to do is to install the root certificate for the CA on your development boxes and away you go.
You have the option to either generate a certificate to use in development, or disabling the use of certificates through the configuration file. I would recommend actually using a certificate also in development.
Extending Leon Breedt's answer, to generate the in-memory x509 certifcate you can use the source code from Keith Elder's SelfCert.
using (CryptContext ctx = new CryptContext())
{
ctx.Open();
var cert = ctx.CreateSelfSignedCertificate(
new SelfSignedCertProperties
{
IsPrivateKeyExportable = true,
KeyBitLength = 4096,
Name = new X500DistinguishedName("cn=InMemoryTestCert"),
ValidFrom = DateTime.Today.AddDays(-1),
ValidTo = DateTime.Today.AddYears(5),
});
var creds = new ServiceCredentials();
creds.UserNameAuthentication.CustomUserNamePasswordValidator = new MyUserNamePasswordValidator();
creds.ServiceCertificate.Certificate = cert;
serviceHost.Description.Behaviors.Add(creds);
}
How about changing the configuration between development and production?
My suggestion would be to consider a couple of different approaches:
For development -> There are ways to generate an SSL certificate locally so that tests with https can be done in an environment that you have total control over.
For "beta" testing -> Consider getting a second certificate for this as there may be a continuous need to do some beta testing between releases so it likely can be used over and over again.