From 47f22a6bd0bb098ba4dfa846334a3e14a4a910f2 Mon Sep 17 00:00:00 2001
From: deuce <>
Date: Thu, 22 Feb 2018 02:12:18 +0000
Subject: [PATCH] Add new private_key object to CryptContext (not in JSDOCS). 
 Values are in the format required by ACMEv2/Let's Encrypt.

Yes, this seems to be the easiest way to exfiltrate a private key from
a cryptlib context.
---
 src/sbbs3/js_cryptcon.c | 231 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 230 insertions(+), 1 deletion(-)

diff --git a/src/sbbs3/js_cryptcon.c b/src/sbbs3/js_cryptcon.c
index ffaa35473d..674b4e7403 100644
--- a/src/sbbs3/js_cryptcon.c
+++ b/src/sbbs3/js_cryptcon.c
@@ -6,6 +6,7 @@
 #include <cryptlib.h>
 #include "js_request.h"
 #include "ssl.h"
+#include "base64.h"
 
 struct private_data {
 	CRYPT_CONTEXT	ctx;
@@ -39,6 +40,226 @@ js_cryptcon_error(JSContext *cx, CRYPT_CONTEXT ctx, int error)
 	free(errstr);
 }
 
+static size_t js_asn1_len(unsigned char *data, size_t len, size_t *off)
+{
+	size_t sz = 0;
+	size_t bytes;
+	size_t i;
+
+	if (*off >= len)
+		return 0;
+
+	if ((data[*off] & 0x80) == 0) {
+		
+		sz = data[*off];
+		(*off)++;
+	}
+	else if(data[*off] == 0x80) {
+		// We can't actually handle this when we're this simple.
+		(*off)++;
+	}
+	else {
+		bytes = data[(*off)++] & 0x7f;
+		for (i = 0; i < bytes && *off < len; i++) {
+			sz <<= 8;
+			sz |= data[(*off)++];
+		}
+	}
+
+	return sz;
+}
+
+static unsigned char js_asn1_type(unsigned char *data, size_t len, size_t *off)
+{
+	unsigned char t = 0;
+
+	if ((data[*off] & 0x1f) == 0x1f) {
+		for ((*off)++; *off < len && data[*off]; (*off)++) {
+			if ((data[*off] & 0x80) == 0)
+				break;
+		}
+	}
+	else {
+		t = data[*off];
+		(*off)++;
+	}
+
+	return t;
+}
+
+static void js_simple_asn1(unsigned char *data, size_t len, JSContext *cx, JSObject *parent)
+{
+	unsigned char t;
+	size_t off=0;
+	size_t sz;
+	char *e;
+	char *n;
+	char *e64;
+	char *n64;
+	JSObject *obj;
+	JSString* estr;
+	JSString* nstr;
+
+	while(off < len) {
+		/* Parse identifier */
+		t = js_asn1_type(data, len, &off);
+
+		/* Parse length */
+		sz = js_asn1_len(data, len, &off);
+
+		switch(t) {
+			case 48:
+				// Sequence, descend into it.
+				break;
+			case 6:
+				// OID.... check if it's PKCS #1
+				if (strncmp((char *)data+off, "\x2a\x86\x48\x86\xF7\x0D\x01\x01\x01", 9)==0) {
+					// YEP!
+					off += sz;
+					off += 9;
+					if (js_asn1_type(data, len, &off) == 2) {
+						sz = js_asn1_len(data,len,&off);
+						n = malloc(sz);
+						if (n == NULL)
+							return;
+						memcpy(n, data+off, sz);
+						n64 = malloc(sz*4/3+3);
+						if (n64 == NULL) {
+							free(n);
+							return;
+						}
+						b64_encode(n64, sz*4/3+3, n, sz);
+						free(n);
+						for (n=n64; *n; n++) {
+							if (*n == '+')
+								*n = '-';
+							else if (*n == '/')
+								*n = '_';
+							else if (*n == '=')
+								*n = 0;
+						}
+						off += sz;
+						if (js_asn1_type(data, len, &off) != 2) {
+							free(n64);
+							return;
+						}
+						sz = js_asn1_len(data,len,&off);
+						e = malloc(sz);
+						if (e == NULL) {
+							free(n64);
+							return;
+						}
+						memcpy(e, data+off, sz);
+						e64 = malloc(sz*4/3+3);
+						if (e64 == NULL) {
+							free(e);
+							free(n64);
+							return;
+						}
+						b64_encode(e64, sz*4/3+3, e, sz);
+						for (e=e64; *e; e++) {
+							if (*e == '+')
+								*e = '-';
+							else if (*e == '/')
+								*e = '_';
+							else if (*e == '=')
+								*e = 0;
+						}
+						free(e);
+						obj=JS_NewObject(cx, NULL, NULL, parent);
+						JS_DefineProperty(cx, parent, "public_key", OBJECT_TO_JSVAL(obj), NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
+						nstr=JS_NewStringCopyZ(cx, n64);
+						if (nstr != NULL)
+							JS_DefineProperty(cx, obj, "n", STRING_TO_JSVAL(nstr), NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
+						estr=JS_NewStringCopyZ(cx, e64);
+						if (estr != NULL)
+							JS_DefineProperty(cx, obj, "e", STRING_TO_JSVAL(estr), NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
+						JS_DeepFreezeObject(cx, obj);
+					}
+					off = len;
+				}
+				off += sz;
+				break;
+			default:
+				off += sz;
+				break;
+		}
+	}
+}
+
+static void js_create_key_object(JSContext *cx, JSObject *parent)
+{
+	struct private_data* p;
+	jsrefcount rc;
+	int status;
+	int val;
+	int sz;
+	CRYPT_CERTIFICATE cert;	// Just to hold the public key...
+	unsigned char *certbuf;
+
+	if ((p=(struct private_data *)JS_GetPrivate(cx,parent))==NULL)
+		return;
+
+	rc = JS_SUSPENDREQUEST(cx);
+
+	status = cryptGetAttribute(p->ctx, CRYPT_CTXINFO_ALGO, &val);
+	if (status != CRYPT_OK) {
+		lprintf(LOG_ERR, "cryptGetAttribute(ALGO) returned %d\n", status);
+		goto resume;
+	}
+	if (val != CRYPT_ALGO_RSA)
+		goto resume;
+
+	status = cryptCreateCert(&cert, CRYPT_UNUSED, CRYPT_CERTTYPE_CERTIFICATE);
+	if (status != CRYPT_OK) {
+		lprintf(LOG_ERR, "cryptCreateCert() returned %d\n", status);
+		goto resume;
+	}
+	status = cryptSetAttribute(cert, CRYPT_CERTINFO_SUBJECTPUBLICKEYINFO, p->ctx);
+	if (status != CRYPT_OK) {
+		lprintf(LOG_ERR, "cryptSetAttribute(PKI) returned %d\n", status);
+		goto resume;
+	}
+	status = cryptSetAttribute(cert, CRYPT_CERTINFO_XYZZY, 1);
+	if (status != CRYPT_OK) {
+		lprintf(LOG_ERR, "cryptSetAttribute(XYZZY) returned %d\n", status);
+		goto resume;
+	}
+	status = cryptSetAttributeString(cert, CRYPT_CERTINFO_COMMONNAME, "Key", 3);
+	if (status != CRYPT_OK) {
+		lprintf(LOG_ERR, "cryptSetAttributeString(CN) returned %d\n", status);
+		goto resume;
+	}
+	status = cryptSignCert(cert, p->ctx);
+	if (status != CRYPT_OK) {
+		lprintf(LOG_ERR, "cryptSignCert() returned %d\n", status);
+		goto resume;
+	}
+	status = cryptExportCert(NULL, 0, &sz, CRYPT_CERTFORMAT_CERTIFICATE, cert);
+	if (status != CRYPT_OK) {
+		lprintf(LOG_ERR, "cryptExportCert(NULL) returned %d\n", status);
+		goto resume;
+	}
+	certbuf = malloc(sz);
+	if (certbuf == NULL) {
+		lprintf(LOG_ERR, "Unable to allocate %d bytes\n", sz);
+		goto resume;
+	}
+	status = cryptExportCert(certbuf, sz, &sz, CRYPT_CERTFORMAT_CERTIFICATE, cert);
+	if (status != CRYPT_OK) {
+		lprintf(LOG_ERR, "cryptExportCert(certbuf) returned %d\n", status);
+		goto resume;
+	}
+	cryptDestroyCert(cert);
+
+	js_simple_asn1(certbuf, sz, cx, parent);
+	free(certbuf);
+
+resume:
+	JS_RESUMEREQUEST(cx, rc);
+	return;
+}
+
 // Destructor
 
 static void
@@ -64,6 +285,7 @@ js_generate_key(JSContext *cx, uintN argc, jsval *arglist)
 	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
 	jsrefcount rc;
 	int status;
+	char kname[32];
 
 	if ((p=(struct private_data *)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx, getprivate_failure, WHERE);
@@ -71,8 +293,13 @@ js_generate_key(JSContext *cx, uintN argc, jsval *arglist)
 	}
 
 	rc = JS_SUSPENDREQUEST(cx);
-	status = cryptGenerateKey(p->ctx);
+	sprintf(kname, "Key-%d", p->ctx);
+	status = cryptSetAttributeString(p->ctx, CRYPT_CTXINFO_LABEL, kname, strlen(kname));
+	if (status == CRYPT_OK)
+		status = cryptGenerateKey(p->ctx);
 	JS_RESUMEREQUEST(cx, rc);
+	if (status == CRYPT_OK)
+		js_create_key_object(cx, obj);
 	if (cryptStatusError(status)) {
 		js_cryptcon_error(cx, p->ctx, status);
 		return JS_FALSE;
@@ -117,6 +344,7 @@ js_set_key(JSContext *cx, uintN argc, jsval *arglist)
 		js_cryptcon_error(cx, p->ctx, status);
 		return JS_FALSE;
 	}
+	js_create_key_object(cx, obj);
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 	return JS_TRUE;
@@ -164,6 +392,7 @@ js_derive_key(JSContext *cx, uintN argc, jsval *arglist)
 		js_cryptcon_error(cx, p->ctx, status);
 		return JS_FALSE;
 	}
+	js_create_key_object(cx, obj);
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 	return JS_TRUE;
-- 
GitLab