diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index 104bcadd62f7361cda09adfebd6974d239036415..077b9ad52c601c327cc183d4288619600029b0d8 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -2,4 +2,5 @@ add_subdirectory( eosd ) add_subdirectory( eosc ) add_subdirectory( eos-walletd ) add_subdirectory( launcher ) -add_subdirectory( codegen ) \ No newline at end of file +add_subdirectory( codegen ) +add_subdirectory( applesedemo ) \ No newline at end of file diff --git a/programs/applesedemo/CMakeLists.txt b/programs/applesedemo/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..90efc2334c88834e4347292d92a3ed074b42ae58 --- /dev/null +++ b/programs/applesedemo/CMakeLists.txt @@ -0,0 +1,28 @@ +if(APPLE) +add_executable( applesedemo main.cpp r1_signature_compactor.cpp ) + + +target_link_libraries( applesedemo + PRIVATE fc ${PLATFORM_SPECIFIC_LIBS} + ) + +set_target_properties(applesedemo PROPERTIES LINK_FLAGS "-framework security -framework corefoundation") + +#Demostration of signing automatically during build; you will need to change parameters for your signing credentials +#[[ +add_custom_command(TARGET applesedemo POST_BUILD + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/sign.sh C5139C2C4D7FA071EFBFD86CE44B652631C9376A 5A4683969Z.one.block.applesedemo /Users/spoon/Library/MobileDevice/Provisioning\ Profiles/95813ad5-e880-432f-85c6-ade3b3298392.provisionprofile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + VERBATIM + ) +]] + +install( TARGETS + applesedemo + + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) + +endif(APPLE) \ No newline at end of file diff --git a/programs/applesedemo/main.cpp b/programs/applesedemo/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d258fbe1ea6ca68617a2a23794ed8ad6ea99506d --- /dev/null +++ b/programs/applesedemo/main.cpp @@ -0,0 +1,311 @@ +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include "r1_signature_compactor.hpp" + +#include +#include +#include + +namespace po = boost::program_options; +using namespace std; +using namespace fc::crypto::r1; + +//get a copy of our key info, should it exist in the keychain +CFDictionaryRef CopyOurKeyInfo() { + const void* keyAttrKeys[] = { + kSecClass, + kSecAttrKeyClass, + kSecAttrApplicationTag, + kSecReturnAttributes, + kSecReturnRef + }; + const void* keyAttrValues[] = { + kSecClassKey, + kSecAttrKeyClassPrivate, + CFSTR("one.block.applesedemo"), + kCFBooleanTrue, + kCFBooleanTrue + }; + CFDictionaryRef keyAttrDic = CFDictionaryCreate(nullptr, keyAttrKeys, keyAttrValues, sizeof(keyAttrKeys)/sizeof(keyAttrKeys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFDictionaryRef attributes = nullptr; + OSStatus ret = SecItemCopyMatching(keyAttrDic, (CFTypeRef*)&attributes); + + CFRelease(keyAttrDic); + return attributes; +} + +SecKeyRef CopyOurKey() { + CFDictionaryRef attributes = CopyOurKeyInfo(); + if(!attributes) + return nullptr; + SecKeyRef key = (SecKeyRef)CFDictionaryGetValue(attributes, kSecValueRef); + if(key) + CFRetain(key); + CFRelease(attributes); + return key; +} + +public_key_data get_compressed_pub_for_key(SecKeyRef key) { + SecKeyRef pubkey = SecKeyCopyPublicKey(key); + + CFErrorRef error = nullptr; + CFDataRef keyrep = SecKeyCopyExternalRepresentation(pubkey, &error); + assert(CFDataGetLength(keyrep) == 65); + + public_key_data pub_key_data; + const UInt8* cfdata = CFDataGetBytePtr(keyrep); + + memcpy(pub_key_data.data+1, cfdata+1, 32); + pub_key_data.data[0] = 0x02 + (cfdata[64]&1); + + CFRelease(keyrep); + CFRelease(pubkey); + + return pub_key_data; +} + +void print_pub_for_key(SecKeyRef key) { + fc::crypto::checksummed_data pub_wrapper; + pub_wrapper.data = get_compressed_pub_for_key(key); + pub_wrapper.check = fc::crypto::checksummed_data::calculate_checksum(pub_wrapper.data, "R1"); + std::vector checksummed = fc::raw::pack(pub_wrapper); + + cout << "public_key(EOSR1" << fc::to_base58(checksummed.data(), checksummed.size()) << ")" << endl; +} + +void print_attributes() { + CFDictionaryRef key_info = CopyOurKeyInfo(); + if(!key_info) + cout << "No key currently in SE" << endl; + else { + CFShow(key_info); + CFRelease(key_info); + } + + SecKeyRef key = CopyOurKey(); + print_pub_for_key(key); + CFRelease(key); +} + +void remove() { + SecKeyRef key = CopyOurKey(); + if(!key) { + cout << "No key current in SE" << endl; + return; + } + CFRelease(key); + //hmmm. previously I was trying to delete based on the key reference; no such luck + + const void* deleteKeys[] = { + kSecClass, + kSecAttrApplicationTag, + }; + const void* deleteValues[] = { + kSecClassKey, + CFSTR("one.block.applesedemo"), + }; + CFDictionaryRef deleteDic = CFDictionaryCreate(nullptr, deleteKeys, deleteValues, sizeof(deleteKeys)/sizeof(deleteKeys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + OSStatus ret = SecItemDelete(deleteDic); + if(!ret) + cout << "Successfully removed key" << endl; + else + cout << "Error removing key " << ret << endl; + + CFRelease(deleteDic); +} + +void create(SecAccessControlCreateFlags flags) { + SecKeyRef key = CopyOurKey(); + if(key) { + cout << "Already have a key in SE; not creating more" << endl; + CFRelease(key); + return; + } + + SecAccessControlRef accessControlRef = SecAccessControlCreateWithFlags(nullptr, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, flags | kSecAccessControlPrivateKeyUsage, nullptr); + + int keySizeValue = 256; + CFNumberRef keySizeNumber = CFNumberCreate(NULL, kCFNumberIntType, &keySizeValue); + + const void* keyAttrKeys[] = { + kSecAttrIsPermanent, + kSecAttrApplicationTag, + kSecAttrAccessControl + }; + const void* keyAttrValues[] = { + kCFBooleanTrue, + CFSTR("one.block.applesedemo"), + accessControlRef + }; + CFDictionaryRef keyAttrDic = CFDictionaryCreate(NULL, keyAttrKeys, keyAttrValues, sizeof(keyAttrKeys)/sizeof(keyAttrKeys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + const void* attrKeys[] = { + kSecAttrKeyType, + kSecAttrKeySizeInBits, + kSecAttrTokenID, + kSecPrivateKeyAttrs + }; + const void* atrrValues[] = { + kSecAttrKeyTypeECSECPrimeRandom, + keySizeNumber, + kSecAttrTokenIDSecureEnclave, + keyAttrDic + }; + CFDictionaryRef attributesDic = CFDictionaryCreate(NULL, attrKeys, atrrValues, sizeof(attrKeys)/sizeof(atrrValues[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFErrorRef error = NULL; + SecKeyRef privateKey = SecKeyCreateRandomKey(attributesDic, &error); + if(error) { + cout << "Failed" << endl; + CFShow(error); + } + else { + cout << "Successfully created" << endl; + print_pub_for_key(privateKey); + CFRelease(privateKey); + } + + + CFRelease(attributesDic); + CFRelease(keyAttrDic); + CFRelease(keySizeNumber); + CFRelease(accessControlRef); +} + +void sign(const string& hex) { + SecKeyRef key = CopyOurKey(); + if(!key) { + cout << "No key currently in SE" << endl; + return; + } + + fc::ecdsa_sig sig = ECDSA_SIG_new(); + const UInt8* der_bytes = nullptr; + fc::sha256 digest(hex); + CFErrorRef error = nullptr; + fc::crypto::r1::compact_signature compacted; + public_key_data pub; + fc::crypto::checksummed_data signature_wrapper; + std::vector checksummed; + std::string blah; + + CFDataRef digestData = CFDataCreateWithBytesNoCopy(nullptr, (UInt8*)digest.data(), digest.data_size(), kCFAllocatorNull); + + //the X9.62 representation of r & s must be exactly 32 bytes or otherwise the "canonical" check fails + while(true) { + CFDataRef signature = SecKeyCreateSignature(key, kSecKeyAlgorithmECDSASignatureDigestX962SHA256, digestData, &error); + if(error) { + cout << "Failed to sign" << endl; + CFShow(error); + goto err; + } + + der_bytes = CFDataGetBytePtr(signature); + assert(der_bytes[0] == 0x30); + assert(der_bytes[2] == 0x02); + assert(der_bytes[4+der_bytes[3]] == 0x02); + + UInt8 rLen, sLen; + rLen = der_bytes[3]; + sLen = der_bytes[5+32]; + CFRelease(signature); + if(rLen == 32 && sLen == 32) + break; + } + + BN_bin2bn(der_bytes+4, der_bytes[3], sig->r); + BN_bin2bn(der_bytes+6+der_bytes[3], der_bytes[4+der_bytes[3]+1], sig->s); + + pub = get_compressed_pub_for_key(key); + + signature_wrapper.data = compact_r1(pub, sig, digest); + signature_wrapper.check = fc::crypto::checksummed_data::calculate_checksum(signature_wrapper.data, "R1"); + checksummed = fc::raw::pack(signature_wrapper); + + cout << "signature(EOSR1" << fc::to_base58(checksummed.data(), checksummed.size()) << ")" << endl; + +err: + CFRelease(key); + CFRelease(digestData); +} + +void recover(vector params) { + fc::sha256 digest(params[0]); + fc::crypto::signature sig(params[1]); + + cout << fc::crypto::public_key(sig, digest) << endl; +} + +int main(int argc, char* argv[]) { + pid_t pid = getpid(); + SecCodeRef code = nullptr; + CFNumberRef pidnumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pid); + CFDictionaryRef piddict = CFDictionaryCreate(kCFAllocatorDefault, (const void**)&kSecGuestAttributePid, (const void**)&pidnumber, 1, nullptr, nullptr); + if(!SecCodeCopyGuestWithAttributes(nullptr, piddict, kSecCSDefaultFlags, &code)) { + if(SecCodeCheckValidity(code, kSecCSDefaultFlags, 0)) { + cout << "This application is NOT signed. For access to the secure enclave the application must be signed. Exiting." << endl; + return 1; + } + CFRelease(code); + } + CFRelease(piddict); + CFRelease(pidnumber); + + po::options_description desc_help; + desc_help.add_options()("help", "halp"); + + po::options_description desc_create("Create and store a key; This application only allows a single key to be stored at a time"); + desc_create.add_options() + ("create-se", "Create a key to be stored in Secure Enclave; no TouchID requirements") + ("create-se-interactive", "Create a key to be stored in Secure Enclave; fingerprint or password required on use") + ("create-se-touch-only", "Create a key to be stored in Secure Enclave; fingerprint required on use"); + + po::options_description desc_key("Other key operations"); + desc_key.add_options() + ("print-attributes", "Print the attributes and public key of the stored key") + ("remove", "Remove the key"); + + po::options_description sign_key("Signing & Key Recovery"); + sign_key.add_options() + ("sign", po::value()->value_name("DIGEST"), "Sign sha256 via SE") + ("recover", po::value>()->multitoken()->value_name("DIGEST COMPACT_SIG"), "Recover and print pubkey"); + + po::options_description all_desc; + all_desc.add(desc_create).add(desc_key).add(sign_key); + po::options_description all_desc_and_help; + all_desc_and_help.add(all_desc).add(desc_help); + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, all_desc_and_help), vm); + po::notify(vm); + + if(vm.count("create-se")) + create(0); + else if(vm.count("create-se-touch-only")) + create(kSecAccessControlTouchIDAny); + else if(vm.count("create-se-interactive")) + create(kSecAccessControlUserPresence); + else if(vm.count("print-attributes")) + print_attributes(); + else if(vm.count("remove")) + remove(); + else if(vm.count("sign")) + sign(vm["sign"].as()); + else if (!vm["recover"].empty() && vm["recover"].as>().size() == 2) + recover(vm["recover"].as>()); + else + cout << all_desc << endl; + + return 0; +} \ No newline at end of file diff --git a/programs/applesedemo/r1_signature_compactor.cpp b/programs/applesedemo/r1_signature_compactor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cb968b382c371783d62379e31661030aeb4bd2b2 --- /dev/null +++ b/programs/applesedemo/r1_signature_compactor.cpp @@ -0,0 +1,129 @@ +#include "r1_signature_compactor.hpp" + +using namespace fc; + +static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) +{ + if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); + + int ret = 0; + BN_CTX *ctx = NULL; + + BIGNUM *x = NULL; + BIGNUM *e = NULL; + BIGNUM *order = NULL; + BIGNUM *sor = NULL; + BIGNUM *eor = NULL; + BIGNUM *field = NULL; + EC_POINT *R = NULL; + EC_POINT *O = NULL; + EC_POINT *Q = NULL; + BIGNUM *rr = NULL; + BIGNUM *zero = NULL; + int n = 0; + int i = recid / 2; + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; } + BN_CTX_start(ctx); + order = BN_CTX_get(ctx); + if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; } + x = BN_CTX_get(ctx); + if (!BN_copy(x, order)) { ret=-1; goto err; } + if (!BN_mul_word(x, i)) { ret=-1; goto err; } + if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; } + field = BN_CTX_get(ctx); + if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; } + if (BN_cmp(x, field) >= 0) { ret=0; goto err; } + if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; } + if (check) + { + if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; } + if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; } + } + if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + n = EC_GROUP_get_degree(group); + e = BN_CTX_get(ctx); + if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; } + if (8*msglen > n) BN_rshift(e, e, 8-(n & 7)); + zero = BN_CTX_get(ctx); + if (!BN_zero(zero)) { ret=-1; goto err; } + if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; } + rr = BN_CTX_get(ctx); + if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; } + sor = BN_CTX_get(ctx); + if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; } + eor = BN_CTX_get(ctx); + if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; } + if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; } + if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; } + + ret = 1; + +err: + if (ctx) { + BN_CTX_end(ctx); + BN_CTX_free(ctx); + } + if (R != NULL) EC_POINT_free(R); + if (O != NULL) EC_POINT_free(O); + if (Q != NULL) EC_POINT_free(Q); + return ret; +} + +fc::crypto::r1::compact_signature compact_r1(fc::crypto::r1::public_key_data& pubkey, fc::ecdsa_sig& sig, fc::sha256& digest) { + fc::crypto::r1::compact_signature csig; + + int nBitsR = BN_num_bits(sig->r); + int nBitsS = BN_num_bits(sig->s); + if (nBitsR <= 256 && nBitsS <= 256) + { + int nRecId = -1; + for (int i=0; i<4; i++) + { + ////public_key keyRec; + EC_KEY* key = EC_KEY_new_by_curve_name( NID_X9_62_prime256v1 ); + if (ECDSA_SIG_recover_key_GFp(key, sig, (unsigned char*)digest.data(), digest.data_size(), i, 1) == 1) + { + EC_KEY_set_conv_form(key, POINT_CONVERSION_COMPRESSED ); + unsigned char* pubcheck = nullptr; + int s = i2o_ECPublicKey(key, &pubcheck); + if (memcmp(pubcheck, pubkey.data, pubkey.size()) == 0) + { + nRecId = i; + free(pubcheck); + break; + } + if(s > 0) + free(pubcheck); + } + } + + if (nRecId == -1) + { + FC_THROW_EXCEPTION( exception, "unable to construct recoverable key"); + } + unsigned char* result = nullptr; + auto bytes = i2d_ECDSA_SIG( sig, &result ); + auto lenR = result[3]; + auto lenS = result[5+lenR]; + auto idxR = 4; + auto idxS = 6+lenR; + if(result[idxR] == 0x00) { + ++idxR; + --lenR; + } + if(result[idxS] == 0x00) { + ++idxS; + --lenS; + } + memcpy( &csig.data[1], &result[idxR], lenR ); + memcpy( &csig.data[33], &result[idxS], lenS ); + free(result); + csig.data[0] = nRecId+27+4; + } + + return csig; +} \ No newline at end of file diff --git a/programs/applesedemo/r1_signature_compactor.hpp b/programs/applesedemo/r1_signature_compactor.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4d19fef483de97893dad0838393e50853a134b24 --- /dev/null +++ b/programs/applesedemo/r1_signature_compactor.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include +#include +#include + +fc::crypto::r1::compact_signature compact_r1(fc::crypto::r1::public_key_data& pubkey, fc::ecdsa_sig& sig, fc::sha256& digest); \ No newline at end of file diff --git a/programs/applesedemo/sign.sh b/programs/applesedemo/sign.sh new file mode 100755 index 0000000000000000000000000000000000000000..0b90c343839d1fd15684d01b5492cbf2c504c28f --- /dev/null +++ b/programs/applesedemo/sign.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +set -eu + +if [ $# -ne 3 ]; then + echo Usage: $0 certificate-fingerprint appid provisioning-file + exit 1 +fi + +CERT=$1 +APPID=$2 +PP=$3 +IFS=. read TEAMID BUNDLEID <<<"${APPID}" + +mkdir -p applesedemo.app/Contents/MacOS +cp "$3" applesedemo.app/Contents/embedded.provisionprofile +cp applesedemo applesedemo.app/Contents/MacOS/applesedemo + +cat > applesedemo.app/Contents/Info.plist < + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + applesedemo + CFBundleIdentifier + $BUNDLEID + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + applesedemo + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1 + + +ENDENDEND + +TMPFILE=$(mktemp /tmp/signscript.XXXXXX) +trap "rm $TMPFILE" EXIT + +cat > $TMPFILE < + + + + com.apple.application-identifier + $APPID + com.apple.developer.team-identifier + $TEAMID + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + +ENDENDEND + +codesign --force --sign $CERT --timestamp=none --entitlements $TMPFILE applesedemo.app