changeset 3:5c8c4fa95803 draft

Added support for transfer chaining and some bugfixes
author Ivo Smits <Ivo@UCIS.nl>
date Mon, 17 Nov 2014 01:19:05 +0100
parents f6954b464d2f
children c642254dc9ee
files marccore.php marcus.php marns_import.php
diffstat 3 files changed, 56 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/marccore.php	Fri Nov 14 00:08:24 2014 +0100
+++ b/marccore.php	Mon Nov 17 01:19:05 2014 +0100
@@ -11,6 +11,7 @@
 			case 'label': return $this->_label;
 			case 'expiration': return (isset($this->_extensions[4]) && strlen($this->_extensions[4]) >= 4) ? marc_decode_int32be($this->_extensions[4]) : NULL;
 			case 'transfer': return isset($this->_extensions[1]) ? $this->_extensions[1] : NULL;
+			case 'transferchain': return isset($this->_extensions[5]) ? $this->_extensions[5] : NULL;
 			case 'value':
 				if ($this->_value === FALSE) $this->_value = self::DecodeValue($this->_message, $this->_valueoffset);
 				return $this->_value;
@@ -30,6 +31,7 @@
 			case 'version': case 'key': case 'serial': case 'label': case 'value': case 'updatemessage': case 'tags': return TRUE;
 			case 'expiration': return isset($this->_extensions[4]);
 			case 'transfer': return isset($this->_extensions[1]);
+			case 'transferchain': return isset($this->_extensions[5]);
 			default: return FALSE;
 		}
 	}
@@ -57,7 +59,7 @@
 		if (!$l || $l < $i+4+1+1) return FALSE;
 		$upd->_serial = marc_decode_int32be($data, $i); $i += 4;
 		$labellen = ord($data[$i++]);
-		if ($l < $i+$labellen+4) return FALSE;
+		if ($l < $i+$labellen+1) return FALSE;
 		$upd->_label = substr($data, $i, $labellen); $i += $labellen;
 		$upd->_extensions = array();
 		for ($numext = ord($data[$i++]); $numext > 0; $numext--) {
@@ -87,6 +89,7 @@
 		if (!isset($upd['key'])) $upd['key'] = substr($seckey, 32, 32);
 		if ($upd['key'] != substr($seckey, 32, 32)) throw new Exception('Resource key is not valid');
 		if (!isset($upd['label'])) throw new Exception('Resource label not set');
+		$upd['label'] = (string)$upd['label'];
 		if (strlen($upd['label']) > 255) throw new Exception('Resource label too long');
 		if (!isset($upd['serial'])) $upd['serial'] = time();
 		if ($current) {
@@ -94,12 +97,35 @@
 			if (!self::CanImport($upd, $current)) throw new Exception('Can not update resource');
 		}
 		if (isset($upd['transfer']) && (strlen($upd['transfer']) != 0 && strlen($upd['transfer']) != NACL_CRYPTO_SIGN_ed25519_PUBLICKEYBYTES)) throw new Exception('Transfer recipient key is not valid');
+		if ($current) {
+			unset($upd['transferchain']);
+			if (isset($current['transferchain']) && ($chain = self::Decode($current['transferchain'])) && $chain->Verify() && $chain->serial >= time() - 365*24*60*60 && isset($chain->transfer) && ($chain->transfer == $upd['key'] || ($upd['key'] == $current['key'] && !strlen($chain->transfer)))) {
+				$upd['transferchain'] = $current['transferchain'];
+			} elseif (isset($current['transfer']) && isset($current['updatemessage']) && $current['serial'] >= time() - 365*24*60*60 && isset($current['transfer']) && ($current['transfer'] == $upd['key'] || !strlen($current['transfer']))) {
+				$upd['transferchain'] = $current['updatemessage'];
+			}
+		}
+		if (isset($upd['transfer'])) {
+			if (isset($upd['transferchain'])) {
+				$chain = self::Decode($upd['transferchain']);
+				while ($chain && $chain->key == $upd['key']) $chain = ($chain->Verify() && $chain->serial >= time() - 365*24*60*60 && isset($chain->transferchain)) ? self::Decode($chain->transferchain) : NULL;
+				if ($chain && $chain->Verify() && $chain->serial >= time() - 365*24*60*60) $upd['transferchain'] = $chain->updatemessage; else unset($upd['transferchain']);
+			}
+			if (isset($upd['value']) && !is_null($upd['value'])) {
+				$chain = array('label' => $upd['label'], 'serial' => $upd['serial'], 'key' => $upd['key'], 'transfer' => $upd['transfer']);
+				if (isset($upd['expiration'])) $chain['expiration'] = $upd['expiration'];
+				if (isset($upd['transferchain'])) $chain['transferchain'] = $upd['transferchain'];
+				$chain = self::Create($chain, $seckey);
+				if ($chain && strlen($chain->updatemessage) <= 0xffff) $upd['transferchain'] = $chain->updatemessage;
+			}
+		}
 		$data = marc_encode_int32be($upd['serial']);
 		$data .= chr(strlen($upd['label'])).$upd['label'];
 		$value = array();
 		if (isset($upd->_extensions)) foreach ($upd->_extensions as $identifier => $item) $value[$identifier] = $item;
 		if (isset($upd['transfer'])) $value[1] = $upd['transfer'];
 		if (isset($upd['expiration'])) $value[4] = marc_encode_int32be($upd['expiration']);
+		if (isset($upd['transferchain'])) $value[5] = $upd['transferchain'];
 		if (count($value) > 0xff) throw new Exception('Too many extensions used');
 		$data .= chr(count($value));
 		foreach ($value as $identifier => $item) {
@@ -118,6 +144,7 @@
 		$arr = array('version' => $this->version, 'key' => $this->key, 'serial' => $this->serial, 'label' => $this->label, 'value' => $this->value);
 		if (isset($this->expiration)) $arr['expiration'] = $this->expiration;
 		if (isset($this->transfer)) $arr['transfer'] = $this->transfer;
+		if (isset($this->transferchain)) $arr['transferchain'] = $this->transferchain;
 		return $arr;
 	}
 
@@ -177,6 +204,7 @@
 	}
 	public static function CanImport($nw, $cu = NULL) {
 		if (!$nw || !isset($nw['label'])) return FALSE;
+		if ($nw['serial'] > time() + 7*24*60*60) return FALSE;
 		if ($cu) {
 			if ($nw['label'] != $cu['label']) return FALSE;
 			if ($cu['serial'] >= $nw['serial']) return FALSE;
@@ -184,12 +212,13 @@
 			if (isset($cu['expiration']) && $cu['expiration'] < time()) return TRUE;
 			if ($cu['serial'] < time() - 365*24*60*60) return TRUE;
 			if (isset($cu['transfer']) && (!strlen($cu['transfer']) || $cu['transfer'] == $nw['key'])) return TRUE;
+			if (isset($nw['transferchain']) && ($chain = MARCUpdate::Decode($nw['transferchain'])) && $chain->Verify() && self::CanImport($nw, $chain) && self::CanImport($chain, $cu)) return TRUE;
 			return FALSE;
 		} else {
 			if ($nw['serial'] < time() - 365*24*60*60) return FALSE;
 			if (isset($nw['expiration']) && $nw['expiration'] < time()) return FALSE;
+			return TRUE;
 		}
-		return TRUE;
 	}
 }
 abstract class MARCDatabase {
@@ -202,25 +231,15 @@
 		if (is_string($resource)) $resource = MARCUpdate::Decode($resource);
 		if (!$resource) return FALSE;
 		$current = $this->GetResource($resource['label']);
-		if (!$force) foreach ($this->_importResourceFilterCallbacks as $callback) if (!call_user_func($callback, $this, $resource, $current)) return FALSE;
-		if (!$force && !MARCUpdate::CanImport($resource, $current)) return FALSE;
+		if (!$force && !$this->CanImportResource($resource, $current)) return FALSE;
 		if (!$resource->Verify()) return FALSE;
 		if (!$this->ImportInternal($resource)) return FALSE;
 		$this->ResourceImported($resource);
 		return $resource;
 	}
-	public function UpdateResource($resource, $seckey, $force = FALSE) {
-		$res = MARCUpdate::Create($resource, $seckey, $force ? NULL : $this->GetResource($resource['label']));
-		if (!$res) return FALSE;
-		return $this->Import($res, $force);
-	}
-	protected function CanImportResource(&$resource, $force = FALSE) {
-		if (is_string($resource)) $resource = MARCUpdate::Decode($resource);
-		if (!$resource) return FALSE;
-		$current = $this->GetResource($resource['label']);
-		if (!$force) foreach ($this->_importResourceFilterCallbacks as $callback) if (!call_user_func($callback, $this, $resource, $current)) return FALSE;
-		if (!$force && !MARCUpdate::CanImport($resource, $current)) return FALSE;
-		if (!$resource->Verify()) return FALSE;
+	protected function CanImportResource($resource, $current) {
+		foreach ($this->_importResourceFilterCallbacks as $callback) if (!call_user_func($callback, $this, $resource, $current)) return FALSE;
+		if (!MARCUpdate::CanImport($resource, $current)) return FALSE;
 		return TRUE;
 	}
 	protected function ResourceImported($resource) {
@@ -232,6 +251,13 @@
 	public function RegisterResourceImportCallback($callback) {
 		$this->_importResourceCallbacks[] = $callback;
 	}
+	public function UpdateResource($resource, $seckey, $force = FALSE) {
+		if (is_a($resource, 'MARCUpdate')) $resource = $resource->ToArray();
+		if (!$force) unset($resource['serial'], $resource['key']);
+		$res = MARCUpdate::Create($resource, $seckey, $force ? NULL : $this->GetResource($resource['label']));
+		if (!$res) return FALSE;
+		return $this->Import($res, $force);
+	}
 	public function SyncHTTP($server, $options = array()) {
 		$log = isset($options['log']) ? $options['log'] : TRUE;
 		$result = array();
--- a/marcus.php	Fri Nov 14 00:08:24 2014 +0100
+++ b/marcus.php	Mon Nov 17 01:19:05 2014 +0100
@@ -81,8 +81,7 @@
 					break;
 				case 'USE':
 					$key = array('store' => FALSE, 'pk' => $resource['key']);
-					if (isset($resource['value']['seckey'])) $key['sk'] = $resource['value']['seckey'];
-					if (isset($resource['value']['seckeyenc'])) $key['locked'] = $resource['value']['seckeyenc'];
+					if (isset($resource['value']['seckey'])) $key['locked'] = $resource['value']['seckey'];
 					break;
 				case 'IMPORT':
 					$key = array('store' => FALSE);
@@ -91,10 +90,13 @@
 					break;
 				case 'UNLOCK':
 					if (!isset($key['locked'])) throw new Exception('The key is not locked');
-					$ret = hash('sha512', $key['pk'].$argv[$argi++], TRUE);
-					$key['sk'] = '';
-					for ($i = 0; $i < 32; $i++) $key['sk'] .= chr(ord($key['locked'][$i]) ^ ord($ret[$i]));
-					$ret = nacl_crypto_sign_ed25519_keypair($key['sk'], $key['sk']);
+					if (!is_array($key['locked']) || !isset($key['locked']['key'])) throw new Exception('The locked key is invalid');
+					$rounds = isset($key['locked']['rounds']) ? intval($key['locked']['rounds']) : 0;
+					$ret = str_repeat(chr(0), 64);
+					for ($i = 0; $i < $rounds; $i++) $ret = hash('sha512', $ret.$argv[$argi].$key['pk'], TRUE);
+					$argi++;
+					$ret = substr($key['locked'] ^ $ret, 0, 32);
+					$ret = nacl_crypto_sign_ed25519_keypair($key['sk'], $ret);
 					if ($ret != $key['pk']) throw new Exception('Key password is not valid');
 					echo 'Unlocked public key '.bin2hex($key['pk'])."\n";
 					break;
@@ -138,9 +140,11 @@
 					break;
 				case 'PWAUTH':
 					if (!isset($key['sk'])) throw new Exception('The key is not available');
-					$ret = hash('sha512', $key['pk'].$argv[$argi++], TRUE);
-					$key['locked'] = '';
-					for ($i = 0; $i < 32; $i++) $key['locked'] .= chr(ord($key['sk'][$i]) ^ ord($ret[$i]));
+					$rounds = 5000;
+					$ret = str_repeat(chr(0), 64);
+					for ($i = 0; $i < $rounds; $i++) $ret = hash('sha512', $ret.$argv[$argi].$key['pk'], TRUE);
+					$argi++;
+					$key['locked'] = array('rounds' => $rounds, 'key' => substr($key['sk'] ^ $hash, 0, 32));
 					if (!isset($resource['value']) || !is_array($resource['value'])) $resource['value'] = array();
 					$resource['value']['seckeyenc'] = $key['locked'];
 					$reschanged = TRUE;
--- a/marns_import.php	Fri Nov 14 00:08:24 2014 +0100
+++ b/marns_import.php	Mon Nov 17 01:19:05 2014 +0100
@@ -91,7 +91,7 @@
 if (file_exists($skey)) {
 	$skey = file_get_contents($skey);
 } else if (strlen($skey) == 64) {
-	$skey = bin2hex($skey);
+	$skey = hex2bin($skey);
 } else if ($skey == '-') {
 	nacl_crypto_sign_ed25519_keypair($skey);
 }