Permalink
Cannot retrieve contributors at this time
/* | |
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers | |
* | |
* Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk> | |
* | |
* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* ---------------------------------------------------------------------------- | |
* This file is designed to be parsed during the build process | |
* | |
* Contains cryptography functions like AES | |
* ---------------------------------------------------------------------------- | |
*/ | |
#include "jsvar.h" | |
#include "jsvariterator.h" | |
#include "jswrap_crypto.h" | |
#include "jsparse.h" | |
#ifdef USE_AES | |
#include "mbedtls/include/mbedtls/aes.h" | |
#endif | |
#ifndef USE_SHA1_JS | |
#include "mbedtls/include/mbedtls/sha1.h" | |
#endif | |
#ifdef USE_SHA256 | |
#include "mbedtls/include/mbedtls/sha256.h" | |
#endif | |
#ifdef USE_SHA512 | |
#include "mbedtls/include/mbedtls/sha512.h" | |
#endif | |
#include "mbedtls/include/mbedtls/pkcs5.h" | |
#ifdef USE_TLS | |
#include "mbedtls/include/mbedtls/pk.h" | |
#include "mbedtls/include/mbedtls/x509.h" | |
#include "mbedtls/include/mbedtls/ssl.h" | |
#endif | |
/*JSON{ | |
"type" : "library", | |
"class" : "crypto", | |
"ifdef" : "USE_CRYPTO" | |
} | |
Cryptographic functions | |
**Note:** This library is currently only included in builds for boards where there is space. For other boards there is `crypto.js` which implements SHA1 in JS. | |
*/ | |
/*JSON{ | |
"type" : "class", | |
"library" : "crypto", | |
"class" : "AES", | |
"ifdef" : "USE_AES" | |
} | |
Class containing AES encryption/decryption | |
**Note:** This library is currently only included in builds for boards where there is space. For other boards there is `crypto.js` which implements SHA1 in JS. | |
*/ | |
/*JSON{ | |
"type" : "staticproperty", | |
"class" : "crypto", | |
"name" : "AES", | |
"generate_full" : "jspNewBuiltin(\"AES\");", | |
"return" : ["JsVar"], | |
"return_object" : "AES", | |
"ifdef" : "USE_AES" | |
} | |
Class containing AES encryption/decryption | |
*/ | |
const char *jswrap_crypto_error_to_str(int err) { | |
switch(err) { | |
#ifdef USE_TLS | |
case MBEDTLS_ERR_X509_INVALID_FORMAT: | |
case MBEDTLS_ERR_PK_KEY_INVALID_FORMAT: | |
return "Invalid format"; | |
case MBEDTLS_ERR_SSL_PK_TYPE_MISMATCH: | |
return "Public key type mismatch"; | |
case MBEDTLS_ERR_X509_ALLOC_FAILED: | |
case MBEDTLS_ERR_SSL_ALLOC_FAILED: | |
case MBEDTLS_ERR_PK_ALLOC_FAILED: | |
#endif | |
case MBEDTLS_ERR_MD_ALLOC_FAILED: return "Not enough memory"; | |
case MBEDTLS_ERR_MD_FEATURE_UNAVAILABLE: return "Feature unavailable"; | |
case MBEDTLS_ERR_MD_BAD_INPUT_DATA: return "Bad input data"; | |
#ifdef USE_AES | |
case MBEDTLS_ERR_AES_INVALID_INPUT_LENGTH: return "Invalid input length"; | |
#endif | |
} | |
return 0; | |
} | |
JsVar *jswrap_crypto_error_to_jsvar(int err) { | |
const char *e = jswrap_crypto_error_to_str(err); | |
if (e) return jsvNewFromString(e); | |
return jsvVarPrintf("-0x%x", -err); | |
} | |
void jswrap_crypto_error(int err) { | |
const char *e = jswrap_crypto_error_to_str(err); | |
if (e) jsError("%s", e); | |
else jsError("Unknown error: -0x%x", -err); | |
} | |
typedef enum { | |
CM_NONE, | |
CM_CBC, | |
CM_CFB, | |
CM_CTR, | |
CM_OFB, | |
CM_ECB, | |
} CryptoMode; | |
CryptoMode jswrap_crypto_getMode(JsVar *mode) { | |
if (jsvIsStringEqual(mode, "CBC")) return CM_CBC; | |
if (jsvIsStringEqual(mode, "CFB")) return CM_CFB; | |
if (jsvIsStringEqual(mode, "CTR")) return CM_CTR; | |
if (jsvIsStringEqual(mode, "OFB")) return CM_OFB; | |
if (jsvIsStringEqual(mode, "ECB")) return CM_ECB; | |
jsExceptionHere(JSET_ERROR, "Unknown Crypto mode %q", mode); | |
return CM_NONE; | |
} | |
mbedtls_md_type_t jswrap_crypto_getHasher(JsVar *hasher) { | |
#ifndef USE_SHA1_JS | |
if (jsvIsStringEqual(hasher, "SHA1")) return MBEDTLS_MD_SHA1; | |
#endif | |
#ifdef USE_SHA256 | |
if (jsvIsStringEqual(hasher, "SHA224")) return MBEDTLS_MD_SHA224; | |
if (jsvIsStringEqual(hasher, "SHA256")) return MBEDTLS_MD_SHA256; | |
#endif | |
#ifdef USE_SHA512 | |
if (jsvIsStringEqual(hasher, "SHA384")) return MBEDTLS_MD_SHA384; | |
if (jsvIsStringEqual(hasher, "SHA512")) return MBEDTLS_MD_SHA512; | |
#endif | |
jsExceptionHere(JSET_ERROR, "Unknown Hasher %q", hasher); | |
return MBEDTLS_MD_NONE; | |
} | |
JsVar *jswrap_crypto_SHAx(JsVar *message, int shaNum) { | |
#ifdef USE_SHA1_JS | |
if (shaNum==1) { | |
// (c) 2016 Rhys Williams, @jumjum. https://github.com/espruino/EspruinoDocs/blob/master/modules/crypto.js | |
return jspExecuteJSFunction("(function(b){function n(a){for(d=3;0<=d;d--)g.push(a>>8*d&255)}var d,a;b=E.toString(b)+'\\x80';var v=new Int32Array([1518500249,1859775393,2400959708,3395469782]);var k=Math.ceil((b.length/4+2)/16);var g=Array(k);b=E.toUint8Array(b);for(d=0;d<k;d++){var f=d<<6;var e=new Int32Array(16);for(a=0;16>a;a++){var c=f+(a<<2);e[a]=b[c]<<24|b[c+1]<<16|b[c+2]<<8|b[c+3]}g[d]=e}g[k-1][14]=8*(b.length-1)/Math.pow(2,32);g[k-1][14]=Math.floor(g[k-1][14]);g[k-1][15]=8*(b.length-1)&4294967295;b=1732584193;var p=4023233417;var q=2562383102;var r=271733878;var t=3285377520;var l=new Int32Array(80);for(d=0;d<k;d++){for(a=0;16>a;a++)l[a]=g[d][a];for(a=16;80>a;a++)f=l[a-3]^l[a-8]^l[a-14]^l[a-16],l[a]=f<<1|f>>>31;f=b;c=p;e=q;var h=r;var u=t;for(a=0;80>a;a++){var m=Math.floor(a/20);var w=f<<5|f>>>27;var x=0===m?c&e^~c&h:1===m?c^e^h:2===m?c&e^c&h^e&h:c^e^h;m=w+x+u+v[m]+l[a]&4294967295;u=h;h=e;e=c<<30|c>>>2;c=f;f=m}b=b+f&4294967295;p=p+c&4294967295;q=q+e&4294967295;r=r+h&4294967295;t=t+u&4294967295}g=[];n(b);n(p);n(q);n(r);n(t);return E.toUint8Array(g).buffer})",0,1,&message); | |
} | |
#endif | |
JSV_GET_AS_CHAR_ARRAY(msgPtr, msgLen, message); | |
if (!msgPtr) return 0; | |
int bufferSize = 20; | |
if (shaNum>1) bufferSize = shaNum/8; | |
char *outPtr = 0; | |
JsVar *outArr = jsvNewArrayBufferWithPtr((unsigned int)bufferSize, &outPtr); | |
if (!outPtr) { | |
jsError("Not enough memory for result"); | |
return 0; | |
} | |
#ifndef USE_SHA1_JS | |
if (shaNum==1) mbedtls_sha1((unsigned char *)msgPtr, msgLen, (unsigned char *)outPtr); | |
#endif | |
#ifdef USE_SHA256 | |
else if (shaNum==224) mbedtls_sha256((unsigned char *)msgPtr, msgLen, (unsigned char *)outPtr, true/*224*/); | |
else if (shaNum==256) mbedtls_sha256((unsigned char *)msgPtr, msgLen, (unsigned char *)outPtr, false/*256*/); | |
#endif | |
#ifdef USE_SHA512 | |
else if (shaNum==384) mbedtls_sha512((unsigned char *)msgPtr, msgLen, (unsigned char *)outPtr, true/*384*/); | |
else if (shaNum==512) mbedtls_sha512((unsigned char *)msgPtr, msgLen, (unsigned char *)outPtr, false/*512*/); | |
#endif | |
return outArr; | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "crypto", | |
"name" : "SHA1", | |
"generate_full" : "jswrap_crypto_SHAx(message, 1)", | |
"params" : [ | |
["message","JsVar","The message to apply the hash to"] | |
], | |
"return" : ["JsVar","Returns a 20 byte ArrayBuffer"], | |
"return_object" : "ArrayBuffer", | |
"ifdef" : "USE_CRYPTO" | |
} | |
Performs a SHA1 hash and returns the result as a 20 byte ArrayBuffer. | |
**Note:** On some boards (currently only Espruino Original) there | |
isn't space for a fully unrolled SHA1 implementation so a slower | |
all-JS implementation is used instead. | |
*/ | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "crypto", | |
"name" : "SHA224", | |
"generate_full" : "jswrap_crypto_SHAx(message, 224)", | |
"params" : [ | |
["message","JsVar","The message to apply the hash to"] | |
], | |
"return" : ["JsVar","Returns a 20 byte ArrayBuffer"], | |
"return_object" : "ArrayBuffer", | |
"ifdef" : "USE_SHA256" | |
} | |
Performs a SHA224 hash and returns the result as a 28 byte ArrayBuffer | |
*/ | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "crypto", | |
"name" : "SHA256", | |
"generate_full" : "jswrap_crypto_SHAx(message, 256)", | |
"params" : [ | |
["message","JsVar","The message to apply the hash to"] | |
], | |
"return" : ["JsVar","Returns a 20 byte ArrayBuffer"], | |
"return_object" : "ArrayBuffer", | |
"ifdef" : "USE_SHA256" | |
} | |
Performs a SHA256 hash and returns the result as a 32 byte ArrayBuffer | |
*/ | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "crypto", | |
"name" : "SHA384", | |
"generate_full" : "jswrap_crypto_SHAx(message, 384)", | |
"params" : [ | |
["message","JsVar","The message to apply the hash to"] | |
], | |
"return" : ["JsVar","Returns a 20 byte ArrayBuffer"], | |
"return_object" : "ArrayBuffer", | |
"ifdef" : "USE_SHA512" | |
} | |
Performs a SHA384 hash and returns the result as a 48 byte ArrayBuffer | |
*/ | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "crypto", | |
"name" : "SHA512", | |
"generate_full" : "jswrap_crypto_SHAx(message, 512)", | |
"params" : [ | |
["message","JsVar","The message to apply the hash to"] | |
], | |
"return" : ["JsVar","Returns a 32 byte ArrayBuffer"], | |
"return_object" : "ArrayBuffer", | |
"ifdef" : "USE_SHA512" | |
} | |
Performs a SHA512 hash and returns the result as a 64 byte ArrayBuffer | |
*/ | |
#ifdef USE_TLS | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "crypto", | |
"name" : "PBKDF2", | |
"generate" : "jswrap_crypto_PBKDF2", | |
"params" : [ | |
["passphrase","JsVar","Passphrase"], | |
["salt","JsVar","Salt for turning passphrase into a key"], | |
["options","JsVar","Object of Options, `{ keySize: 8 (in 32 bit words), iterations: 10, hasher: 'SHA1'/'SHA224'/'SHA256'/'SHA384'/'SHA512' }`"] | |
], | |
"return" : ["JsVar","Returns an ArrayBuffer"], | |
"return_object" : "ArrayBuffer", | |
"ifdef" : "USE_TLS" | |
} | |
Password-Based Key Derivation Function 2 algorithm, using SHA512 | |
*/ | |
JsVar *jswrap_crypto_PBKDF2(JsVar *passphrase, JsVar *salt, JsVar *options) { | |
int iterations = 1; | |
int keySize = 128/32; | |
mbedtls_md_type_t hasher = MBEDTLS_MD_SHA1; | |
if (jsvIsObject(options)) { | |
keySize = jsvGetIntegerAndUnLock(jsvObjectGetChild(options, "keySize", 0)); | |
if (keySize<=0) keySize=128/32; | |
iterations = jsvGetIntegerAndUnLock(jsvObjectGetChild(options, "iterations", 0)); | |
if (iterations<1) iterations = 1; | |
JsVar *hashVar = jsvObjectGetChild(options, "hasher", 0); | |
if (!jsvIsUndefined(hashVar)) | |
hasher = jswrap_crypto_getHasher(hashVar); | |
jsvUnLock(hashVar); | |
} else if (!jsvIsUndefined(options)) | |
jsError("Options should be an object or undefined, got %t", options); | |
if (hasher == MBEDTLS_MD_NONE) | |
return 0; // already shown an error | |
JSV_GET_AS_CHAR_ARRAY(passPtr, passLen, passphrase); | |
if (!passPtr) return 0; | |
JSV_GET_AS_CHAR_ARRAY(saltPtr, saltLen, salt); | |
if (!saltPtr) return 0; | |
int err; | |
mbedtls_md_context_t ctx; | |
mbedtls_md_init( &ctx ); | |
err = mbedtls_md_setup( &ctx, mbedtls_md_info_from_type( hasher ), 1 ); | |
assert(err==0); | |
char *keyPtr = 0; | |
JsVar *keyArr = jsvNewArrayBufferWithPtr((unsigned)keySize*4, &keyPtr); | |
if (!keyPtr) { | |
jsError("Not enough memory for result"); | |
return 0; | |
} | |
err = mbedtls_pkcs5_pbkdf2_hmac( &ctx, | |
(unsigned char*)passPtr, passLen, | |
(unsigned char*)saltPtr, saltLen, | |
(unsigned)iterations, | |
(unsigned)keySize*4, (unsigned char*)keyPtr ); | |
mbedtls_md_free( &ctx ); | |
if (!err) { | |
return keyArr; | |
} else { | |
jswrap_crypto_error(err); | |
jsvUnLock(keyArr); | |
return 0; | |
} | |
} | |
#endif | |
#ifdef USE_AES | |
static NO_INLINE JsVar *jswrap_crypto_AEScrypt(JsVar *message, JsVar *key, JsVar *options, bool encrypt) { | |
int err; | |
unsigned char iv[16]; // initialisation vector | |
memset(iv, 0, 16); | |
CryptoMode mode = CM_CBC; | |
if (jsvIsObject(options)) { | |
JsVar *ivVar = jsvObjectGetChild(options, "iv", 0); | |
if (ivVar) { | |
jsvIterateCallbackToBytes(ivVar, iv, sizeof(iv)); | |
jsvUnLock(ivVar); | |
} | |
JsVar *modeVar = jsvObjectGetChild(options, "mode", 0); | |
if (!jsvIsUndefined(modeVar)) | |
mode = jswrap_crypto_getMode(modeVar); | |
jsvUnLock(modeVar); | |
if (mode == CM_NONE) return 0; | |
} else if (!jsvIsUndefined(options)) { | |
jsError("'options' must be undefined, or an Object"); | |
return 0; | |
} | |
mbedtls_aes_context aes; | |
mbedtls_aes_init( &aes ); | |
JSV_GET_AS_CHAR_ARRAY(messagePtr, messageLen, message); | |
if (!messagePtr) return 0; | |
JSV_GET_AS_CHAR_ARRAY(keyPtr, keyLen, key); | |
if (!keyPtr) return 0; | |
if (encrypt) | |
err = mbedtls_aes_setkey_enc( &aes, (unsigned char*)keyPtr, (unsigned int)keyLen*8 ); | |
else | |
err = mbedtls_aes_setkey_dec( &aes, (unsigned char*)keyPtr, (unsigned int)keyLen*8 ); | |
if (err) { | |
jswrap_crypto_error(err); | |
return 0; | |
} | |
char *outPtr = 0; | |
JsVar *outVar = jsvNewArrayBufferWithPtr((unsigned int)messageLen, &outPtr); | |
if (!outPtr) { | |
jsError("Not enough memory for result"); | |
return 0; | |
} | |
switch (mode) { | |
case CM_CBC: | |
err = mbedtls_aes_crypt_cbc( &aes, | |
encrypt ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT, | |
messageLen, | |
iv, | |
(unsigned char*)messagePtr, | |
(unsigned char*)outPtr ); | |
break; | |
case CM_CFB: | |
err = mbedtls_aes_crypt_cfb8( &aes, | |
encrypt ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT, | |
messageLen, | |
iv, | |
(unsigned char*)messagePtr, | |
(unsigned char*)outPtr ); | |
break; | |
case CM_CTR: { | |
size_t nc_off = 0; | |
unsigned char nonce_counter[16]; | |
unsigned char stream_block[16]; | |
memset(nonce_counter, 0, sizeof(nonce_counter)); | |
memset(stream_block, 0, sizeof(stream_block)); | |
err = mbedtls_aes_crypt_ctr( &aes, | |
messageLen, | |
&nc_off, | |
nonce_counter, | |
stream_block, | |
(unsigned char*)messagePtr, | |
(unsigned char*)outPtr ); | |
break; | |
} | |
case CM_ECB: { | |
size_t i = 0; | |
while (!err && i+15 < messageLen) { | |
err = mbedtls_aes_crypt_ecb( &aes, | |
encrypt ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT, | |
(unsigned char*)&messagePtr[i], | |
(unsigned char*)&outPtr[i] ); | |
i += 16; | |
} | |
break; | |
} | |
default: | |
err = MBEDTLS_ERR_MD_FEATURE_UNAVAILABLE; | |
break; | |
} | |
mbedtls_aes_free( &aes ); | |
if (!err) { | |
return outVar; | |
} else { | |
jswrap_crypto_error(err); | |
jsvUnLock(outVar); | |
return 0; | |
} | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "AES", | |
"name" : "encrypt", | |
"generate" : "jswrap_crypto_AES_encrypt", | |
"params" : [ | |
["passphrase","JsVar","Message to encrypt"], | |
["key","JsVar","Key to encrypt message - must be an ArrayBuffer of 128, 192, or 256 BITS"], | |
["options","JsVar","An optional object, may specify `{ iv : new Uint8Array(16), mode : 'CBC|CFB|CTR|OFB|ECB' }`"] | |
], | |
"return" : ["JsVar","Returns an ArrayBuffer"], | |
"return_object" : "ArrayBuffer", | |
"ifdef" : "USE_AES" | |
} | |
*/ | |
JsVar *jswrap_crypto_AES_encrypt(JsVar *message, JsVar *key, JsVar *options) { | |
return jswrap_crypto_AEScrypt(message, key, options, true); | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "AES", | |
"name" : "decrypt", | |
"generate" : "jswrap_crypto_AES_decrypt", | |
"params" : [ | |
["passphrase","JsVar","Message to decrypt"], | |
["key","JsVar","Key to encrypt message - must be an ArrayBuffer of 128, 192, or 256 BITS"], | |
["options","JsVar","An optional object, may specify `{ iv : new Uint8Array(16), mode : 'CBC|CFB|CTR|OFB|ECB' }`"] | |
], | |
"return" : ["JsVar","Returns an ArrayBuffer"], | |
"return_object" : "ArrayBuffer", | |
"ifdef" : "USE_AES" | |
} | |
*/ | |
JsVar *jswrap_crypto_AES_decrypt(JsVar *message, JsVar *key, JsVar *options) { | |
return jswrap_crypto_AEScrypt(message, key, options, false); | |
} | |
#endif |