Check-in [0defa19481]
Overview
Comment:More X.509v3 support for SSH agent
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:0defa19481e970a38bb8ff91c2a4cbb1ff6ea66e
User & Date: rkeene on 2019-02-04 17:32:30
Other Links: manifest | tags
Context
2019-02-04
23:17
Better matching of requested blob to one of our certificates check-in: fd3c997789 user: rkeene tags: trunk
17:32
More X.509v3 support for SSH agent check-in: 0defa19481 user: rkeene tags: trunk
2019-01-31
15:04
Added support for x509v3-ssh-rsa and x509v3-sign-rsa to agent check-in: 648368d41d user: rkeene tags: trunk
Changes

Modified build/chrome/ssh-agent.js from [d4659c8b69] to [f7f7a0c55f].

   130    130   }
   131    131   
   132    132   function cackeySSHAgentEncodeArray(input) {
   133    133   	var result;
   134    134   
   135    135   	result = cackeySSHAgentEncodeInt(input.length);
   136    136   	input.forEach(function(element) {
   137         -		result = result.concat(element);
          137  +		result = result.concat(cackeySSHAgentEncodeLV(element));
   138    138   	});
   139    139   
   140    140   	return(result);
   141    141   }
          142  +
          143  +function cackeySSHAgentDecodeArray(input) {
          144  +	var items, info;
          145  +	var itemCount;
          146  +
          147  +	info = cackeySSHAgentDecodeInt(input);
          148  +	input = info.output;
          149  +	itemCount = info.value;
          150  +
          151  +	items = [];
          152  +	while (itemCount > 0) {
          153  +		itemCount--;
          154  +
          155  +		info = cackeySSHAgentDecodeLV(input);
          156  +		input = info.output;
          157  +		items.push(info.value);
          158  +	}
          159  +
          160  +	return({
          161  +		value: items,
          162  +		output: input
          163  +	});
          164  +}
          165  +
   142    166   
   143    167   function cackeySSHAgentEncodeToUTF8Array(str) {
   144    168   	var utf8 = [];
   145    169   
   146    170   	if (typeof(str) === "string") {
   147    171   		str = str.split("").map(function(c) {
   148    172   			return(c.charCodeAt(0));
................................................................................
   205    229   			buffer = buffer.join("");
   206    230   			break;
   207    231   	}
   208    232   
   209    233   	return(buffer);
   210    234   }
   211    235   
   212         -function cackeySSHAgentEncodeCertToKeyAndID(cert, keyType) {
          236  +function cackeySSHAgentEncodeCertToKeyAndID(cert, sshKeyType) {
   213    237   	var result = null, resultKey = null;
   214    238   	var certObj, certBytes;
   215    239   	var publicKey;
   216    240   
   217    241   	certObj = new X509;
   218    242   	if (!certObj) {
   219    243   		return(result);
................................................................................
   221    245   
   222    246   	certBytes = Array.from(new Uint8Array(cert));
   223    247   
   224    248   	certObj.readCertHex(cackeySSHAgentEncodeBinaryToHex(certBytes));
   225    249   
   226    250   	publicKey = certObj.getPublicKey();
   227    251   
   228         -	switch (keyType) {
          252  +	switch (sshKeyType) {
   229    253   		case "ssh":
   230    254   			switch (publicKey.type) {
   231    255   				case "RSA":
   232    256   					resultKey = cackeySSHAgentEncodeString("ssh-rsa");
   233    257   					resultKey = resultKey.concat(cackeySSHAgentEncodeBigInt(publicKey.e));
   234    258   					resultKey = resultKey.concat(cackeySSHAgentEncodeBigInt(publicKey.n));
   235    259   					break;
   236    260   				default:
   237         -					console.log("[cackeySSH] Unsupported public key type:", keyType, "/", publicKey.type, "-- ignoring.");
          261  +					console.log("[cackeySSH] Unsupported public key type:", sshKeyType, "/", publicKey.type, "-- ignoring.");
   238    262   					break;
   239    263   			}
   240    264   			break;
   241    265   		case "x509v3-sign":
   242    266   			resultKey = certBytes;
   243    267   			break;
   244    268   		case "x509v3-ssh":
................................................................................
   246    270   				case "RSA":
   247    271   					resultKey = cackeySSHAgentEncodeString("x509v3-ssh-rsa");
   248    272   
   249    273   					/*
   250    274   					 * Array of certificates
   251    275   					 */
   252    276   					resultKey = resultKey.concat(cackeySSHAgentEncodeArray([
   253         -						cackeySSHAgentEncodeLV(certBytes)
          277  +						certBytes
   254    278   					]));
   255    279   
   256    280   					/*
   257    281   					 * Array of OCSP responses
   258    282   					 */
   259    283   					resultKey = resultKey.concat(cackeySSHAgentEncodeArray([]));
   260    284   					break;
   261    285   				default:
   262         -					console.log("[cackeySSH] Unsupported public key type:", keyType, "/", publicKey.type, "-- ignoring.");
          286  +					console.log("[cackeySSH] Unsupported public key type:", sshKeyType, "/", publicKey.type, "-- ignoring.");
   263    287   					break;
   264    288   			}
   265    289   			break;
   266    290   		default:
   267         -			console.log("[cackeySSH] Unsupported SSH key type:", keyType, "-- ignoring.");
          291  +			console.log("[cackeySSH] Unsupported SSH key type:", sshKeyType, "-- ignoring.");
   268    292   			break;
   269    293   	}
   270    294   
   271    295   	if (resultKey) {
          296  +		var certLabel;
          297  +		var certSAN;
          298  +		var ignoreException;
          299  +
          300  +		/*
          301  +		 * Set a default label
          302  +		 */
          303  +		certLabel = certObj.getSubjectString();
          304  +
          305  +		/*
          306  +		 * Try to find a better label from the certificate's
          307  +		 * Subject Alternative Name (SAN) extensions
          308  +		 */
          309  +		try {
          310  +			certSAN = certObj.getExtSubjectAltName2();
          311  +			certSAN.forEach(function(itemPair) {
          312  +				var itemType, itemValue;
          313  +
          314  +				itemType = itemPair[0];
          315  +				itemValue = itemPair[1];
          316  +
          317  +				if (itemType === "MAIL") {
          318  +					certLabel = itemValue;
          319  +				}
          320  +			});
          321  +		} catch (ignoreException) {
          322  +		}
          323  +
   272    324   		result = {
   273         -			id: certObj.getSubjectString(),
          325  +			label: certLabel,
   274    326   			publicKeyType: publicKey.type,
   275         -			sshKeyType: keyType,
          327  +			sshKeyType: sshKeyType,
   276    328   			key: resultKey
   277    329   		};
   278    330   	}
   279    331   
   280    332   	return(result);
   281    333   }
          334  +
          335  +function cackeySSHAgentDecodeCert(requestArray) {
          336  +}
   282    337   
   283    338   /*
   284    339    * Command Handlers
   285    340    */
   286    341   async function cackeySSHAgentCommandRequestIdentity(request) {
   287    342   	var response;
   288    343   	var certs = [];
................................................................................
   313    368   	 */
   314    369   	response = [];
   315    370   
   316    371   	response.push(cackeySSHAgentMessage.SSH_AGENT_IDENTITIES_ANSWER);
   317    372   	response = response.concat(cackeySSHAgentEncodeInt(keys.length));
   318    373   	keys.forEach(function(key) {
   319    374   		response = response.concat(cackeySSHAgentEncodeLV(key.key));
   320         -		response = response.concat(cackeySSHAgentEncodeString("CACKey: " + key.id));
          375  +		response = response.concat(cackeySSHAgentEncodeString(key.label));
   321    376   	});
   322    377   
   323    378   	return(response);
   324    379   }
   325    380   
   326    381   async function cackeySSHAgentCommandSignRequest(request) {
   327    382   	var keyInfo, data, flags;
   328    383   	var certs, certToUse, certToUseType;
   329    384   	var hashMethod, signedData, signedDataHeader, signRequest;
   330         -	var response;
          385  +	var decryptedData, decryptRequest;
          386  +	var operation, response;
   331    387   	var flagMeaning = {
   332    388   		SSH_AGENT_RSA_SHA2_256: 2,
   333         -		SSH_AGENT_RSA_SHA2_512: 4
          389  +		SSH_AGENT_RSA_SHA2_512: 4,
          390  +		SSH_AGENT_RSA_RAW:      0x40000000,
          391  +		SSH_AGENT_RSA_DECRYPT:  0x80000000
   334    392   	};
   335    393   
          394  +	/*
          395  +	 * Default mode is signing
          396  +	 */
          397  +	operation = "sign";
          398  +
   336    399   	/*
   337    400   	 * Strip off the command
   338    401   	 */
   339    402   	request = request.slice(1);
   340    403   
   341    404   	/*
   342    405   	 * Get certificate to sign using
................................................................................
   393    456   		case "RSA":
   394    457   			if ((flags & flagMeaning.SSH_AGENT_RSA_SHA2_512) == flagMeaning.SSH_AGENT_RSA_SHA2_512) {
   395    458   				hashMethod = "SHA512";
   396    459   				data = await crypto.subtle.digest("SHA-512", new Uint8Array(data));
   397    460   			} else if ((flags & flagMeaning.SSH_AGENT_RSA_SHA2_256) == flagMeaning.SSH_AGENT_RSA_SHA2_256) {
   398    461   				hashMethod = "SHA256";
   399    462   				data = await crypto.subtle.digest("SHA-256", new Uint8Array(data));
          463  +			} else if (flags == (flagMeaning.SSH_AGENT_RSA_RAW | flagMeaning.SSH_AGENT_RSA_DECRYPT)) {
          464  +				operation = "decrypt";
          465  +				data = new Uint8Array(data);
          466  +			} else if (flags == flagMeaning.SSH_AGENT_RSA_RAW) {
          467  +				hashMethod = "RAW";
          468  +				data = new Uint8Array(data);
   400    469   			} else if (flags == 0) {
   401    470   				hashMethod = "SHA1";
   402    471   				data = await crypto.subtle.digest("SHA-1", new Uint8Array(data));
   403    472   			} else {
   404    473   				console.info("[cackeySSH] Sign request with flags set to", flags, "which is unsupported, failing the request.");
   405    474   
   406    475   				return(null);
   407    476   			}
   408    477   
   409    478   			switch (hashMethod) {
          479  +				case "RAW":
          480  +					signedDataHeader = cackeySSHAgentEncodeString("rsa");
          481  +					break;
   410    482   				case "SHA1":
   411    483   					signedDataHeader = cackeySSHAgentEncodeString("ssh-rsa");
   412    484   					break;
   413    485   				case "SHA256":
   414    486   					signedDataHeader = cackeySSHAgentEncodeString("rsa-sha2-256");
   415    487   					break;
   416    488   				case "SHA512":
................................................................................
   427    499   			console.info("[cackeySSH] Unsupported public key type:", certToUseType, "failing the request.");
   428    500   
   429    501   			return(null);
   430    502   			break;
   431    503   	}
   432    504   
   433    505   	/*
   434         -	 * Sign the data
          506  +	 * Sign or decrypt the data
   435    507   	 */
   436         -	signRequest = {
   437         -		hash: hashMethod,
   438         -		digest: new Uint8Array(data)
   439         -	};
   440         -	signedData = await cackeySignMessage(signRequest);
   441         -	signedData = Array.from(new Uint8Array(signedData));
          508  +	switch (operation) {
          509  +		case "sign":
          510  +			signRequest = {
          511  +				hash: hashMethod,
          512  +				certificate: certToUse.certificate,
          513  +				digest: new Uint8Array(data)
          514  +			};
          515  +
          516  +			if (goog.DEBUG) {
          517  +				console.log("[cackeySSH] Requesting CACKey sign message:", signRequest);
          518  +			}
          519  +
          520  +			signedData = await cackeySignMessage(signRequest);
          521  +			signedData = Array.from(new Uint8Array(signedData));
          522  +			break;
          523  +		case "decrypt":
          524  +			/* XXX:TODO: Incomplete ! */
          525  +			decryptRequest = {
          526  +				data: data
          527  +			}
          528  +			break;
          529  +	}
   442    530   
   443    531   	/*
   444    532   	 * Encode signature
   445    533   	 */
   446    534   	signedData = signedDataHeader.concat(cackeySSHAgentEncodeLV(signedData));
   447    535   
   448    536   	/*
................................................................................
   458    546   
   459    547   /*
   460    548    * Session handling
   461    549    */
   462    550   async function cackeySSHAgentHandleMessage(socket, request) {
   463    551   	var sshRequestID, sshRequest, response, sshResponse;
   464    552   	var sshHandlerError;
          553  +	var postMessageException;
   465    554   
   466    555   	if (!request.type || request.type !== "auth-agent@openssh.com") {
   467    556   		return;
   468    557   	}
   469    558   
   470    559   	if (!request.data || request.data.length < 1) {
   471    560   		return;
................................................................................
   505    594   		data: response
   506    595   	};
   507    596   
   508    597   	if (goog.DEBUG) {
   509    598   		console.log("[cackeySSH] Response: ", sshResponse);
   510    599   	}
   511    600   
   512         -	socket.postMessage(sshResponse);
          601  +	try {
          602  +		socket.postMessage(sshResponse);
          603  +	} catch (postMessageException) {
          604  +		if (goog.DEBUG) {
          605  +			console.log("[cackeySSH] Failed to send response", postMessageException);
          606  +		}
          607  +	}
   513    608   
   514    609   	return;
   515    610   }
   516    611   
   517    612   function cackeySSHAgentAcceptConnection(socket) {
   518    613   	if (!socket) {
   519    614   		return;