Diff

Differences From Artifact [0cbe74a449]:

To Artifact [757d0fe647]:


     1         -#! /home/rkeene/tmp/cackey/build/tcl/tclkit
            1  +#! /usr/bin/env tclsh
     2      2   
            3  +lappend auto_path /home/rkeene/devel/tcl-duktape/build/work /home/rkeene/devel/tuapi /home/rkeene/devel/tclpkcs11-fossil/build/work {*}[glob -nocomplain -directory /opt/appfs/rkeene.org/tcllib/platform/latest/lib/ tcllib*]
            4  +
            5  +package provide pki 0.10
            6  +catch {
            7  +	source /home/rkeene/devel/tcllib-pki/pki.tcl
            8  +}
     3      9   package require duktape
     4     10   package require tuapi
           11  +package require pki::pkcs11
           12  +
           13  +proc pkcs11ModuleHandle {} {
           14  +	if {![info exists ::pkcs11ModuleHandle]} {
           15  +		set ::pkcs11ModuleHandle [::pki::pkcs11::loadmodule /home/rkeene/tmp/cackey/build/tcl/softokn3-pkcs11.so]
           16  +	}
           17  +	return $::pkcs11ModuleHandle
           18  +}
           19  +
           20  +proc pkcs11ModuleUnload {handle} {
           21  +	if {[info exists ::pkcs11ModuleHandle] && $handle eq $::pkcs11ModuleHandle} {
           22  +		unset ::pkcs11ModuleHandle
           23  +	}
           24  +	::pki::pkcs11::unloadmodule $handle
           25  +}
           26  +
           27  +proc addRSAToJS {jsHandle} {
           28  +	::duktape::tcl-function $jsHandle __parseCert json {cert} {
           29  +		set cert [binary decode hex $cert]
           30  +		if {[catch {
           31  +			set cert [::pki::x509::parse_cert $cert]
           32  +		}]} {
           33  +			return ""
           34  +		}
           35  +
           36  +		set e [format %llx [dict get $cert e]]
           37  +		set n [format %llx [dict get $cert n]]
           38  +		if {[string length $e] % 2 != 0} {
           39  +			set e "0$e"
           40  +		}
           41  +		if {[string length $n] % 2 != 0} {
           42  +			set n "0$n"
           43  +		}
           44  +		if {[string length $n] % 4 == 0} {
           45  +			set n "00$n"
           46  +		}
           47  +
           48  +		set retval "\{
           49  +			\"publicKey\": \{
           50  +				\"type\":\"[string toupper [dict get $cert type]]\",
           51  +				\"e\":\"$e\",
           52  +				\"n\":\"$n\"
           53  +			\},
           54  +			\"subject\": \"[dict get $cert subject]\",
           55  +			\"issuer\": \"[dict get $cert issuer]\",
           56  +			\"serial\": \"[dict get $cert serial_number]\"
           57  +		\}"
           58  +
           59  +		return $retval
           60  +	}
           61  +
           62  +	::duktape::tcl-function $jsHandle __crypto_subtle_digest bytearray {hash data} {
           63  +		switch -exact -- $hash {
           64  +			"SHA-256" {
           65  +				package require sha256
           66  +				return [::sha2::sha256 -- $data]
           67  +			}
           68  +			"SHA-1" {
           69  +				package require sha1
           70  +				return [::sha1::sha1 -- $data]
           71  +			}
           72  +			default {
           73  +				error "Hash not supported: $hash"
           74  +			}
           75  +		}
           76  +	}
           77  +
           78  +	::duktape::eval $jsHandle {
           79  +		crypto.subtle.digest.internal = __crypto_subtle_digest;
           80  +		delete __crypto_subtle_digest;
           81  +	}
           82  +
           83  +	::duktape::eval $jsHandle {
           84  +		function X509() {
           85  +			this.hex = "";
           86  +			this.readCertHex = function(string) {
           87  +				this.hex = string;
           88  +			};
           89  +			this.computeCertData = function() {
           90  +				if (this.certData) {
           91  +					return;
           92  +				}
           93  +				this.certData = X509.parseCert(this.hex);
           94  +				this.certData.publicKey.n = Duktape.dec('hex', this.certData.publicKey.n);
           95  +				this.certData.publicKey.e = Duktape.dec('hex', this.certData.publicKey.e);
           96  +			}
           97  +			this.getPublicKey = function() {
           98  +				this.computeCertData();
           99  +				return(this.certData.publicKey);
          100  +			};
          101  +			this.getSubjectString = function() {
          102  +				this.computeCertData();
          103  +				return(this.certData.subject);
          104  +			};
          105  +			this.getExtSubjectAltName2 = function() {
          106  +				return([]);
          107  +			}
          108  +		}
          109  +		X509.parseCert = __parseCert;
          110  +		delete __parseCert;
          111  +	}
          112  +}
     5    113   
     6    114   proc initSSHAgent {} {
     7         -	if {[info exists ::jsHandle]} {
     8         -		return
          115  +	foreach file {chrome-emu.js ssh-agent-noasync.js} {
          116  +		unset -nocomplain fd
          117  +		catch {
          118  +			set fd [open $file]
          119  +			set js($file) [read $fd]
          120  +		}
          121  +		catch {
          122  +			close $fd
          123  +		}
          124  +	}
          125  +
          126  +	set jsHandle [::duktape::init -safe true]
          127  +
          128  +	::duktape::tcl-function $jsHandle __puts {args} {
          129  +		if {[llength $args] ni {1 2}} {
          130  +			return -code error "wrong # args: puts ?{stderr|stdout}? message"
          131  +		}
          132  +		if {[llength $args] == 2} {
          133  +			set chan [lindex $args 0]
          134  +			if {$chan ni {stdout stderr}} {
          135  +				return -code error "Only stderr and stdout allowed"
          136  +			}
          137  +		}
          138  +		puts {*}$args
          139  +	}
          140  +
          141  +	::duktape::eval $jsHandle {
          142  +		runtime = {};
          143  +		runtime.puts = __puts;
          144  +		runtime.stderr = "stderr";
          145  +		delete __puts;
     9    146   	}
    10    147   
    11         -	set chromeEmuJS [read [open chrome-emu.js]]
    12         -	set sshAgentJS [read [open ssh-agent-noasync.js]]
    13         -
    14         -	set ::jsHandle [::duktape::init]
    15         -
    16         -	::duktape::eval $::jsHandle $chromeEmuJS
    17         -	::duktape::eval $::jsHandle $sshAgentJS
    18         -
    19         -	puts [::duktape::eval $::jsHandle {
    20         -		chrome.runtime.connectCallbacks[0]({
    21         -			sender: {
          148  +	::duktape::eval $jsHandle {var goog = {DEBUG: false};}
          149  +	::duktape::eval $jsHandle $js(chrome-emu.js)
          150  +	addRSAToJS $jsHandle
          151  +	::duktape::eval $jsHandle $js(ssh-agent-noasync.js)
          152  +	::duktape::eval $jsHandle {cackeySSHAgentFeatures.enabled = true;}
          153  +	::duktape::eval $jsHandle {cackeySSHAgentFeatures.includeCerts = true;}
          154  +	::duktape::eval $jsHandle {
          155  +		function connection(callback) {
          156  +			this.sender = {
    22    157   				id: "pnhechapfaindjhompbnflcldabbghjo"
    23         -			},
    24         -			onMessage: {
    25         -				addListener: function() {
    26         -					/* XXX:TODO */
          158  +			};
          159  +			this.onMessage = {
          160  +				listeners: [],
          161  +				addListener: function(callback) {
          162  +					this.listeners.push(callback);
    27    163   				}
          164  +			};
          165  +			this.postMessage = function(message) {
          166  +				return(callback(this, message));
          167  +			};
          168  +			this.send = function(message) {
          169  +				this.onMessage.listeners.forEach(function(listener) {
          170  +					listener(message);
          171  +				});
          172  +			};
          173  +		}
          174  +
          175  +		function handleDataFromAgent(socket, data) {
          176  +			if (!data || !data.type || !data.data) {
          177  +				return;
          178  +			}
          179  +
          180  +			if (data.type != "auth-agent@openssh.com") {
          181  +				return;
          182  +			}
          183  +
          184  +			writeFramed(socket.handle, data.data);
          185  +		}
          186  +
          187  +		function handleDataFromSocket(socket, data) {
          188  +			socket.send({
          189  +				type: "auth-agent@openssh.com",
          190  +				data: Array.from(data)
          191  +			});
          192  +		}
          193  +
          194  +		function writeFramed(sock, data) {
          195  +			var buffer;
          196  +			var idx;
          197  +
          198  +			buffer = new Buffer(data.length);
          199  +			for (idx = 0; idx < data.length; idx++) {
          200  +				buffer[idx] = data[idx];
          201  +			}
          202  +			return(writeFramedBuffer(sock, buffer));
          203  +		}
          204  +
          205  +		function cackeyListCertificates() {
          206  +			var certs;
          207  +			var certObjs;
          208  +
          209  +			certObjs = [];
          210  +			certs = cackeyListCertificatesBare();
          211  +			certs.forEach(function(cert) {
          212  +				certObjs.push({
          213  +					certificate: new Uint8Array(cert),
          214  +					supportedHashes: ['SHA1', 'SHA256', 'SHA512', 'MD5_SHA1']
          215  +				});
          216  +			});
          217  +
          218  +			return(certObjs);
          219  +		}
          220  +
          221  +		function cackeySignMessage(request) {
          222  +			var retval;
          223  +			var digest, digestHeader;
          224  +
          225  +			/*
          226  +			 * XXX:TODO: Pull this out of cackey.js into a common.js
          227  +			 */
          228  +			switch (request.hash) {
          229  +				case "SHA1":
          230  +					digestHeader = new Uint8Array([0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14]);
          231  +					break;
          232  +				case "SHA256":
          233  +					digestHeader = new Uint8Array([0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20]);
          234  +					break;
          235  +				case "SHA512":
          236  +					digestHeader = new Uint8Array([0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40]);
          237  +					break;
          238  +				case "MD5_SHA1":
          239  +				case "RAW":
          240  +					digestHeader = new Uint8Array();
          241  +					break;
          242  +				default:
          243  +					console.error("[cackey] Asked to sign a message with a hash we do not support: " + request.hash);
          244  +					return(null);
          245  +			}
          246  +
          247  +			digest = Array.from(digestHeader);
          248  +			digest = digest.concat(Array.from(request.digest));
          249  +			digest = new Buffer(digest);
          250  +
          251  +			retval = cackeySignBare(request.certificate, digest);
          252  +
          253  +			return(retval);
          254  +		}
          255  +	}
          256  +
          257  +	::duktape::tcl-function $jsHandle writeFramedBuffer {sock message} {
          258  +		set dataLen [string length $message]
          259  +		set dataLen [binary format I $dataLen]
          260  +		puts -nonewline $sock "${dataLen}${message}"
          261  +		flush $sock
          262  +
          263  +		return ""
          264  +	}
          265  +
          266  +	::duktape::tcl-function $jsHandle readFramed bytearray {sock} {
          267  +		catch {
          268  +			set dataLen [read $sock 4]
          269  +		}
          270  +		if {![info exists dataLen] || [string length $dataLen] != 4} {
          271  +			close $sock
          272  +			return
          273  +		}
          274  +
          275  +		binary scan $dataLen I dataLen
          276  +
          277  +		set data [read $sock $dataLen]
          278  +
          279  +		return $data
          280  +	}
          281  +
          282  +
          283  +	::duktape::tcl-function $jsHandle cackeySignBare bytearray {cert message} {
          284  +		set handle [pkcs11ModuleHandle]
          285  +		set certInfo [listCerts $handle $cert]
          286  +		if {![dict exists $certInfo pkcs11_slotid]} {
          287  +			pkcs11ModuleUnload $handle
          288  +			return -code error "Unable to find certificate to sign with"
          289  +		}
          290  +
          291  +		set slotId [dict get $certInfo pkcs11_slotid]
          292  +		set data [::pki::sign $message $certInfo raw]
          293  +
          294  +		return $data
          295  +	}
          296  +
          297  +	::duktape::tcl-function $jsHandle cackeyListCertificatesBare {arraylist bytearray} {} {
          298  +		set handle [pkcs11ModuleHandle]
          299  +		return [listCerts $handle]
          300  +	}
          301  +
          302  +	return $jsHandle
          303  +}
          304  +
          305  +proc listCerts {handle {match ""}} {
          306  +	set certs [list]
          307  +
          308  +	set slots [::pki::pkcs11::listslots $handle]
          309  +	foreach slotInfo $slots {
          310  +		set slotId [lindex $slotInfo 0]
          311  +		set slotLabel [lindex $slotInfo 1]
          312  +		set slotFlags [lindex $slotInfo 2]
          313  +
          314  +		set slotCerts [::pki::pkcs11::listcerts $handle $slotId]
          315  +		foreach keyList $slotCerts {
          316  +			set cert [dict get $keyList raw]
          317  +			set cert [binary decode hex $cert]
          318  +			if {$match eq $cert} {
          319  +				return $keyList
    28    320   			}
    29         -		})
    30         -	}]
          321  +			lappend certs $cert
          322  +		}
          323  +	}
          324  +
          325  +	if {$match ne ""} {
          326  +		return [list]
          327  +	}
          328  +
          329  +	return $certs
          330  +}
          331  +
          332  +proc handleData {sock jsHandle} {
          333  +	if {[catch {
          334  +		::duktape::eval $jsHandle {handleDataFromSocket(socket, readFramed(socket.handle));}
          335  +	}]} {
          336  +		puts stderr "ERROR: $::errorInfo"
          337  +		close $sock
          338  +	}
          339  +}
          340  +
          341  +proc incomingConnection {sock args} {
          342  +	if {[catch {
          343  +		set jsHandle [initSSHAgent]
          344  +
          345  +		::duktape::eval $jsHandle {var socket = new connection(handleDataFromAgent);}
          346  +		::duktape::eval $jsHandle "socket.handle = \"$sock\";"
          347  +		::duktape::eval $jsHandle {chrome.runtime.externalConnect(socket);}
          348  +
          349  +		fconfigure $sock -translation binary -encoding binary -blocking true
          350  +		fileevent $sock readable [list handleData $sock $jsHandle]
          351  +	}]} {
          352  +		puts stderr "ERROR: $::errorInfo"
          353  +		close $sock
          354  +	}
    31    355   }
    32    356   
    33         -initSSHAgent
          357  +::tuapi::syscall::socket_unix -server incomingConnection "./agent"
          358  +
          359  +vwait forever