Skip to content

Commit

Permalink
Code from Mike Hearn
Browse files Browse the repository at this point in the history
  • Loading branch information
gavinandresen committed Nov 20, 2012
0 parents commit 93926d4
Show file tree
Hide file tree
Showing 10 changed files with 2,719 additions and 0 deletions.
19 changes: 19 additions & 0 deletions README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
To compile and run, do something like this:

$ g++ -g -o invoice-create -lssl -lcrypto -I/opt/local/include -L/opt/local/lib -lprotobuf invoice-create.cpp invoices.pb.cc && ./invoice-create
File written successfully, see demo.bitcoin-invoice
You can check it by running invoice-verify

$ g++ -o invoice-verify -lssl -lcrypto -I/opt/local/include -L/opt/local/lib -lprotobuf invoice-verify.cpp invoices.pb.cc && ./invoice-verify
Invoice is valid! Signed by www.plan99.net
Label: Bobs Widget Emporium

Files:
- ca-bundle-startcom.pem: A bunch of trusted root authorities provided by StartCom Ltd.
- pubcert.pem: An expired certificate issued to me for plan99.net
- privkey.pem: The private key, this originally came appended to pubcert.pem and I split them.
- sub.class1.server.ca.pem: Intermediate cert for StartCom certificates

The intermediate cert is not an unusual requirement for SSL authorities. You are expected
to provide it to your web server software along with your regular certificate so the chain
can be formed back to the root CA.
653 changes: 653 additions & 0 deletions ca-bundle-startcom.pem

Large diffs are not rendered by default.

116 changes: 116 additions & 0 deletions invoice-create.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Writing out a demo invoice file. Loads certs from PEM files
// converts them to binary DER form and writes them out to a
// protocol buffer.

// Apple has deprecated OpenSSL in latest MacOS, shut up compiler warnings about it.
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"

#include <string>
#include <iostream>
#include <fstream>
#include <assert.h>

#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include <openssl/evp.h>

#include "invoices.pb.h";

using std::string;

// Returns the files contents as a byte array.
string load_file(const char *path) {
string result;
std::ifstream cert_file(path);
result.assign(std::istreambuf_iterator<char>(cert_file), std::istreambuf_iterator<char>());
return result;
}

// Must be freed with BIO_free.
BIO *string_to_bio(const string &str) {
return BIO_new_mem_buf((void*)str.data(), str.size());
}

// Take textual PEM data (concatenated base64 encoded x509 data with separator markers)
// and return an X509 object suitable for verification or use.
X509 *parse_pem_cert(string cert_data) {
// Parse it into an X509 structure.
BIO *bio = string_to_bio(cert_data);
X509 *cert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
assert(cert);
BIO_free(bio);
return cert;
}

string x509_to_der(X509 *cert) {
unsigned char *buf = NULL;
int buflen = i2d_X509(cert, &buf);
string data((char*)buf, buflen);
return data;
}

int main(int argc, char **argv) {
SSL_library_init();
ERR_load_BIO_strings();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;

// Load my expired demo cert.
X509 *my_cert = parse_pem_cert(load_file("pubcert.pem"));
// Load StartComs intermediate cert. A real tool would let you specify all intermediate
// certs you need to reach a root CA, or load from a config file or whatever.
X509 *intermediate_cert = parse_pem_cert(load_file("sub.class1.server.ca.pem"));

// Build the invoice and add the certs to it.
Invoice invoice;
invoice.set_label("Bobs Widget Emporium");
IdentityData *id_data = invoice.mutable_identity_data();
id_data->add_cert_chain(x509_to_der(intermediate_cert));
id_data->add_cert_chain(x509_to_der(my_cert));
Output *output = invoice.add_outputs();
output->set_value(1000); // 1000 satoshis
output->set_script("this should obviously be binary data");

// Serialize the invoice in preparation for signing.
string data_to_sign;
invoice.SerializeToString(&data_to_sign);

// Now we want to sign the invoice using the privkey that matches the cert.
// There are many key formats and some keys can be password protected. We gloss
// over all of that here and just assume unpassworded PEM.
BIO *pkey = string_to_bio(load_file("privkey.pem"));
EVP_PKEY *privkey = PEM_read_bio_PrivateKey(pkey, NULL, NULL, NULL);
assert(privkey);

EVP_MD_CTX ctx;
EVP_MD_CTX_init(&ctx);
assert(EVP_SignInit_ex(&ctx, EVP_sha256(), NULL));
assert(EVP_SignUpdate(&ctx, data_to_sign.data(), data_to_sign.size()));
unsigned char *signature = new unsigned char[EVP_PKEY_size(privkey)];
unsigned int actual_signature_len;
assert(EVP_SignFinal(&ctx, signature, &actual_signature_len, privkey));

// Now we have our signature, let's check it actually verifies.
EVP_PKEY *pubkey = X509_get_pubkey(my_cert);
EVP_MD_CTX_init(&ctx);
assert(EVP_VerifyInit_ex(&ctx, EVP_sha256(), NULL));
assert(EVP_VerifyUpdate(&ctx, data_to_sign.data(), data_to_sign.size()));
assert(EVP_VerifyFinal(&ctx, signature, actual_signature_len, pubkey));

// We got here, so the signature is self-consistent. Put it into the protobuf.
string sigstr((char*)signature, actual_signature_len);
id_data->set_signature(sigstr);

std::fstream outfile("demo.bitcoin-invoice", std::ios::out | std::ios::trunc | std::ios::binary);
assert(invoice.SerializeToOstream(&outfile));
printf("File written successfully, see demo.bitcoin-invoice\n");
printf("You can check it by running invoice-verify\n");

google::protobuf::ShutdownProtobufLibrary();
}
127 changes: 127 additions & 0 deletions invoice-verify.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Demo of how to verify X509 cert chain without an SSL connection.

// Apple has deprecated OpenSSL in latest MacOS, shut up compiler warnings about it.
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"

#include <string>
#include <fstream>
#include <assert.h>

#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>

#include "invoices.pb.h";

using std::string;

// Take binary DER data and return an X509 object suitable for verification or use.
X509 *parse_der_cert(string cert_data) {
const unsigned char *data = (const unsigned char *)cert_data.data();
X509 *cert = d2i_X509(NULL, &data, cert_data.size());
assert(cert);
return cert;
}

int main(int argc, char **argv) {
SSL_library_init();
ERR_load_BIO_strings();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;

// Load all the cert authorities.
// The cert store will have all of the certificates for the trusted root authorities.
X509_STORE *cert_store = X509_STORE_new();
// This tells the store to find certificates by loading them from a file.
// The X509_LOOKUP_file() method here is actually just telling OpenSSL
// how to do it, it doesn't actually look anything up despite the name.
X509_LOOKUP *lookup = X509_STORE_add_lookup(cert_store, X509_LOOKUP_file());
// Load all the root authority certificates. This file was retrieved from
// http://www.startssl.com/certs/ca-bundle.pem on Nov 18th 2012.
assert(X509_LOOKUP_load_file(lookup, "ca-bundle-startcom.pem", X509_FILETYPE_PEM));

// Load the invoice file.
std::ifstream infile("demo.bitcoin-invoice", std::ios::in | std::ios::binary);
Invoice invoice;
assert(invoice.ParseFromIstream(&infile));

// Dump in raw text format, obviously this is mostly useless as bulk of
// the data is binary.
// printf("%s\n", invoice.DebugString().c_str());

// Load the certs from the invoice.
const IdentityData &id_data = invoice.identity_data();
std::vector<X509*> certs;
for (int i = 0; i < id_data.cert_chain_size(); i++) {
X509 *cert = parse_der_cert(id_data.cert_chain(i));
certs.push_back(cert);
}
assert(certs.size() > 0);

// The last cert is the signing cert, the rest are untrusted certs that chain
// to a valid root authority. OpenSSL needs them separately.
STACK_OF(X509) *chain = sk_X509_new_null();
for (int i = 0; i < certs.size() - 1; i++) {
sk_X509_push(chain, certs[i]);
}
X509 *signing_cert = certs[certs.size() - 1];

// Now create a "store context", which is a single use object for checking,
// load the signing cert into it and verify.
X509_STORE_CTX *store_ctx = X509_STORE_CTX_new();
assert(X509_STORE_CTX_init(store_ctx, cert_store, signing_cert, chain));
// Override the verification time so Mikes expired cert can be used as a demo.
// The verify param ownership is taken by the store context. Set this to false
// to see "cert expired" as an error. Don't just comment out the set_time call
// as setting an empty verify param apparently causes other issues.
if (true) {
X509_VERIFY_PARAM *vpm = X509_VERIFY_PARAM_new();
X509_VERIFY_PARAM_set_time(vpm, static_cast<time_t>(1293843600));
X509_STORE_CTX_set0_param(store_ctx, vpm);
}
// Now do the verification!
int result = X509_verify_cert(store_ctx);
X509_NAME *certname = X509_get_subject_name(signing_cert);
if (result < 0) {
int error = X509_STORE_CTX_get_error(store_ctx);
printf("%d: %s\n", error, X509_verify_cert_error_string(error));
exit(1);
}

// The cert is valid. Now we need to check the signature. Start by deleting it
// from the protobuf.
string signature = id_data.signature();
invoice.mutable_identity_data()->clear_signature();
string data_to_verify;
invoice.SerializeToString(&data_to_verify);

EVP_MD_CTX ctx;
EVP_PKEY *pubkey = X509_get_pubkey(signing_cert);
EVP_MD_CTX_init(&ctx);
assert(EVP_VerifyInit_ex(&ctx, EVP_sha256(), NULL));
assert(EVP_VerifyUpdate(&ctx, data_to_verify.data(), data_to_verify.size()));
assert(EVP_VerifyFinal(&ctx, (const unsigned char*)signature.data(), signature.size(), pubkey));

// OpenSSL API for getting human printable strings from certs is baroque.
int textlen = X509_NAME_get_text_by_NID(certname, NID_commonName, NULL, 0);
char *website = new char[textlen + 1];
assert(X509_NAME_get_text_by_NID(certname, NID_commonName, website, textlen + 1) == textlen);
printf("Invoice is valid! Signed by %s\n", website);
printf("Label: %s\n", invoice.label().c_str());

// Avoid reported memory leaks.
delete website;
X509_STORE_CTX_free(store_ctx);
for (int i = 0; i < certs.size(); i++)
X509_free(certs[i]);
X509_STORE_free(cert_store);
google::protobuf::ShutdownProtobufLibrary();
}



Loading

0 comments on commit 93926d4

Please sign in to comment.