comparison marccore.php @ 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 3ac7bd7495fd
children c642254dc9ee
comparison
equal deleted inserted replaced
2:f6954b464d2f 3:5c8c4fa95803
9 case 'key': return $this->_key; 9 case 'key': return $this->_key;
10 case 'serial': return $this->_serial; 10 case 'serial': return $this->_serial;
11 case 'label': return $this->_label; 11 case 'label': return $this->_label;
12 case 'expiration': return (isset($this->_extensions[4]) && strlen($this->_extensions[4]) >= 4) ? marc_decode_int32be($this->_extensions[4]) : NULL; 12 case 'expiration': return (isset($this->_extensions[4]) && strlen($this->_extensions[4]) >= 4) ? marc_decode_int32be($this->_extensions[4]) : NULL;
13 case 'transfer': return isset($this->_extensions[1]) ? $this->_extensions[1] : NULL; 13 case 'transfer': return isset($this->_extensions[1]) ? $this->_extensions[1] : NULL;
14 case 'transferchain': return isset($this->_extensions[5]) ? $this->_extensions[5] : NULL;
14 case 'value': 15 case 'value':
15 if ($this->_value === FALSE) $this->_value = self::DecodeValue($this->_message, $this->_valueoffset); 16 if ($this->_value === FALSE) $this->_value = self::DecodeValue($this->_message, $this->_valueoffset);
16 return $this->_value; 17 return $this->_value;
17 case 'updatemessage': return $this->_message; 18 case 'updatemessage': return $this->_message;
18 case 'tags': return $this->_tags; break; 19 case 'tags': return $this->_tags; break;
28 public function __isset($name) { 29 public function __isset($name) {
29 switch (strtolower($name)) { 30 switch (strtolower($name)) {
30 case 'version': case 'key': case 'serial': case 'label': case 'value': case 'updatemessage': case 'tags': return TRUE; 31 case 'version': case 'key': case 'serial': case 'label': case 'value': case 'updatemessage': case 'tags': return TRUE;
31 case 'expiration': return isset($this->_extensions[4]); 32 case 'expiration': return isset($this->_extensions[4]);
32 case 'transfer': return isset($this->_extensions[1]); 33 case 'transfer': return isset($this->_extensions[1]);
34 case 'transferchain': return isset($this->_extensions[5]);
33 default: return FALSE; 35 default: return FALSE;
34 } 36 }
35 } 37 }
36 public function __unset($name) { 38 public function __unset($name) {
37 switch (strtolower($name)) { 39 switch (strtolower($name)) {
55 $i = NACL_CRYPTO_SIGN_ed25519_PUBLICKEYBYTES + 64 + 1; 57 $i = NACL_CRYPTO_SIGN_ed25519_PUBLICKEYBYTES + 64 + 1;
56 $l = strlen($data); 58 $l = strlen($data);
57 if (!$l || $l < $i+4+1+1) return FALSE; 59 if (!$l || $l < $i+4+1+1) return FALSE;
58 $upd->_serial = marc_decode_int32be($data, $i); $i += 4; 60 $upd->_serial = marc_decode_int32be($data, $i); $i += 4;
59 $labellen = ord($data[$i++]); 61 $labellen = ord($data[$i++]);
60 if ($l < $i+$labellen+4) return FALSE; 62 if ($l < $i+$labellen+1) return FALSE;
61 $upd->_label = substr($data, $i, $labellen); $i += $labellen; 63 $upd->_label = substr($data, $i, $labellen); $i += $labellen;
62 $upd->_extensions = array(); 64 $upd->_extensions = array();
63 for ($numext = ord($data[$i++]); $numext > 0; $numext--) { 65 for ($numext = ord($data[$i++]); $numext > 0; $numext--) {
64 if ($l < $i+1+2) return FALSE; 66 if ($l < $i+1+2) return FALSE;
65 $extid = ord($data[$i++]); 67 $extid = ord($data[$i++]);
85 if (strlen($seckey) == 32) nacl_crypto_sign_ed25519_keypair($seckey, $seckey); 87 if (strlen($seckey) == 32) nacl_crypto_sign_ed25519_keypair($seckey, $seckey);
86 if (strlen($seckey) < 64) throw new Exception('Signing key is not valid'); 88 if (strlen($seckey) < 64) throw new Exception('Signing key is not valid');
87 if (!isset($upd['key'])) $upd['key'] = substr($seckey, 32, 32); 89 if (!isset($upd['key'])) $upd['key'] = substr($seckey, 32, 32);
88 if ($upd['key'] != substr($seckey, 32, 32)) throw new Exception('Resource key is not valid'); 90 if ($upd['key'] != substr($seckey, 32, 32)) throw new Exception('Resource key is not valid');
89 if (!isset($upd['label'])) throw new Exception('Resource label not set'); 91 if (!isset($upd['label'])) throw new Exception('Resource label not set');
92 $upd['label'] = (string)$upd['label'];
90 if (strlen($upd['label']) > 255) throw new Exception('Resource label too long'); 93 if (strlen($upd['label']) > 255) throw new Exception('Resource label too long');
91 if (!isset($upd['serial'])) $upd['serial'] = time(); 94 if (!isset($upd['serial'])) $upd['serial'] = time();
92 if ($current) { 95 if ($current) {
93 if ($upd['serial'] <= $current['serial']) $upd['serial'] = $current['serial'] + 1; 96 if ($upd['serial'] <= $current['serial']) $upd['serial'] = $current['serial'] + 1;
94 if (!self::CanImport($upd, $current)) throw new Exception('Can not update resource'); 97 if (!self::CanImport($upd, $current)) throw new Exception('Can not update resource');
95 } 98 }
96 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'); 99 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');
100 if ($current) {
101 unset($upd['transferchain']);
102 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)))) {
103 $upd['transferchain'] = $current['transferchain'];
104 } elseif (isset($current['transfer']) && isset($current['updatemessage']) && $current['serial'] >= time() - 365*24*60*60 && isset($current['transfer']) && ($current['transfer'] == $upd['key'] || !strlen($current['transfer']))) {
105 $upd['transferchain'] = $current['updatemessage'];
106 }
107 }
108 if (isset($upd['transfer'])) {
109 if (isset($upd['transferchain'])) {
110 $chain = self::Decode($upd['transferchain']);
111 while ($chain && $chain->key == $upd['key']) $chain = ($chain->Verify() && $chain->serial >= time() - 365*24*60*60 && isset($chain->transferchain)) ? self::Decode($chain->transferchain) : NULL;
112 if ($chain && $chain->Verify() && $chain->serial >= time() - 365*24*60*60) $upd['transferchain'] = $chain->updatemessage; else unset($upd['transferchain']);
113 }
114 if (isset($upd['value']) && !is_null($upd['value'])) {
115 $chain = array('label' => $upd['label'], 'serial' => $upd['serial'], 'key' => $upd['key'], 'transfer' => $upd['transfer']);
116 if (isset($upd['expiration'])) $chain['expiration'] = $upd['expiration'];
117 if (isset($upd['transferchain'])) $chain['transferchain'] = $upd['transferchain'];
118 $chain = self::Create($chain, $seckey);
119 if ($chain && strlen($chain->updatemessage) <= 0xffff) $upd['transferchain'] = $chain->updatemessage;
120 }
121 }
97 $data = marc_encode_int32be($upd['serial']); 122 $data = marc_encode_int32be($upd['serial']);
98 $data .= chr(strlen($upd['label'])).$upd['label']; 123 $data .= chr(strlen($upd['label'])).$upd['label'];
99 $value = array(); 124 $value = array();
100 if (isset($upd->_extensions)) foreach ($upd->_extensions as $identifier => $item) $value[$identifier] = $item; 125 if (isset($upd->_extensions)) foreach ($upd->_extensions as $identifier => $item) $value[$identifier] = $item;
101 if (isset($upd['transfer'])) $value[1] = $upd['transfer']; 126 if (isset($upd['transfer'])) $value[1] = $upd['transfer'];
102 if (isset($upd['expiration'])) $value[4] = marc_encode_int32be($upd['expiration']); 127 if (isset($upd['expiration'])) $value[4] = marc_encode_int32be($upd['expiration']);
128 if (isset($upd['transferchain'])) $value[5] = $upd['transferchain'];
103 if (count($value) > 0xff) throw new Exception('Too many extensions used'); 129 if (count($value) > 0xff) throw new Exception('Too many extensions used');
104 $data .= chr(count($value)); 130 $data .= chr(count($value));
105 foreach ($value as $identifier => $item) { 131 foreach ($value as $identifier => $item) {
106 $item = (string)$item; 132 $item = (string)$item;
107 if (strlen($item) > 0xffff) throw new Exception('Extension data too big'); 133 if (strlen($item) > 0xffff) throw new Exception('Extension data too big');
116 } 142 }
117 public function ToArray() { 143 public function ToArray() {
118 $arr = array('version' => $this->version, 'key' => $this->key, 'serial' => $this->serial, 'label' => $this->label, 'value' => $this->value); 144 $arr = array('version' => $this->version, 'key' => $this->key, 'serial' => $this->serial, 'label' => $this->label, 'value' => $this->value);
119 if (isset($this->expiration)) $arr['expiration'] = $this->expiration; 145 if (isset($this->expiration)) $arr['expiration'] = $this->expiration;
120 if (isset($this->transfer)) $arr['transfer'] = $this->transfer; 146 if (isset($this->transfer)) $arr['transfer'] = $this->transfer;
147 if (isset($this->transferchain)) $arr['transferchain'] = $this->transferchain;
121 return $arr; 148 return $arr;
122 } 149 }
123 150
124 private static function EncodeValue($data) { 151 private static function EncodeValue($data) {
125 if (is_null($data)) { 152 if (is_null($data)) {
175 default: throw new Exception('Unsupported type code '.$type); 202 default: throw new Exception('Unsupported type code '.$type);
176 } 203 }
177 } 204 }
178 public static function CanImport($nw, $cu = NULL) { 205 public static function CanImport($nw, $cu = NULL) {
179 if (!$nw || !isset($nw['label'])) return FALSE; 206 if (!$nw || !isset($nw['label'])) return FALSE;
207 if ($nw['serial'] > time() + 7*24*60*60) return FALSE;
180 if ($cu) { 208 if ($cu) {
181 if ($nw['label'] != $cu['label']) return FALSE; 209 if ($nw['label'] != $cu['label']) return FALSE;
182 if ($cu['serial'] >= $nw['serial']) return FALSE; 210 if ($cu['serial'] >= $nw['serial']) return FALSE;
183 if ($cu['key'] == $nw['key']) return TRUE; 211 if ($cu['key'] == $nw['key']) return TRUE;
184 if (isset($cu['expiration']) && $cu['expiration'] < time()) return TRUE; 212 if (isset($cu['expiration']) && $cu['expiration'] < time()) return TRUE;
185 if ($cu['serial'] < time() - 365*24*60*60) return TRUE; 213 if ($cu['serial'] < time() - 365*24*60*60) return TRUE;
186 if (isset($cu['transfer']) && (!strlen($cu['transfer']) || $cu['transfer'] == $nw['key'])) return TRUE; 214 if (isset($cu['transfer']) && (!strlen($cu['transfer']) || $cu['transfer'] == $nw['key'])) return TRUE;
215 if (isset($nw['transferchain']) && ($chain = MARCUpdate::Decode($nw['transferchain'])) && $chain->Verify() && self::CanImport($nw, $chain) && self::CanImport($chain, $cu)) return TRUE;
187 return FALSE; 216 return FALSE;
188 } else { 217 } else {
189 if ($nw['serial'] < time() - 365*24*60*60) return FALSE; 218 if ($nw['serial'] < time() - 365*24*60*60) return FALSE;
190 if (isset($nw['expiration']) && $nw['expiration'] < time()) return FALSE; 219 if (isset($nw['expiration']) && $nw['expiration'] < time()) return FALSE;
191 } 220 return TRUE;
192 return TRUE; 221 }
193 } 222 }
194 } 223 }
195 abstract class MARCDatabase { 224 abstract class MARCDatabase {
196 private $_importResourceFilterCallbacks = array(), $_importResourceCallbacks = array(); 225 private $_importResourceFilterCallbacks = array(), $_importResourceCallbacks = array();
197 public abstract function GetResource($label); 226 public abstract function GetResource($label);
200 public abstract function DeleteResource($label); 229 public abstract function DeleteResource($label);
201 public function Import($resource, $force = FALSE) { 230 public function Import($resource, $force = FALSE) {
202 if (is_string($resource)) $resource = MARCUpdate::Decode($resource); 231 if (is_string($resource)) $resource = MARCUpdate::Decode($resource);
203 if (!$resource) return FALSE; 232 if (!$resource) return FALSE;
204 $current = $this->GetResource($resource['label']); 233 $current = $this->GetResource($resource['label']);
205 if (!$force) foreach ($this->_importResourceFilterCallbacks as $callback) if (!call_user_func($callback, $this, $resource, $current)) return FALSE; 234 if (!$force && !$this->CanImportResource($resource, $current)) return FALSE;
206 if (!$force && !MARCUpdate::CanImport($resource, $current)) return FALSE;
207 if (!$resource->Verify()) return FALSE; 235 if (!$resource->Verify()) return FALSE;
208 if (!$this->ImportInternal($resource)) return FALSE; 236 if (!$this->ImportInternal($resource)) return FALSE;
209 $this->ResourceImported($resource); 237 $this->ResourceImported($resource);
210 return $resource; 238 return $resource;
211 } 239 }
240 protected function CanImportResource($resource, $current) {
241 foreach ($this->_importResourceFilterCallbacks as $callback) if (!call_user_func($callback, $this, $resource, $current)) return FALSE;
242 if (!MARCUpdate::CanImport($resource, $current)) return FALSE;
243 return TRUE;
244 }
245 protected function ResourceImported($resource) {
246 foreach ($this->_importResourceCallbacks as $callback) call_user_func($callback, $this, $resource);
247 }
248 public function RegisterResourceFilterCallback($callback) {
249 $this->_importResourceFilterCallbacks[] = $callback;
250 }
251 public function RegisterResourceImportCallback($callback) {
252 $this->_importResourceCallbacks[] = $callback;
253 }
212 public function UpdateResource($resource, $seckey, $force = FALSE) { 254 public function UpdateResource($resource, $seckey, $force = FALSE) {
255 if (is_a($resource, 'MARCUpdate')) $resource = $resource->ToArray();
256 if (!$force) unset($resource['serial'], $resource['key']);
213 $res = MARCUpdate::Create($resource, $seckey, $force ? NULL : $this->GetResource($resource['label'])); 257 $res = MARCUpdate::Create($resource, $seckey, $force ? NULL : $this->GetResource($resource['label']));
214 if (!$res) return FALSE; 258 if (!$res) return FALSE;
215 return $this->Import($res, $force); 259 return $this->Import($res, $force);
216 }
217 protected function CanImportResource(&$resource, $force = FALSE) {
218 if (is_string($resource)) $resource = MARCUpdate::Decode($resource);
219 if (!$resource) return FALSE;
220 $current = $this->GetResource($resource['label']);
221 if (!$force) foreach ($this->_importResourceFilterCallbacks as $callback) if (!call_user_func($callback, $this, $resource, $current)) return FALSE;
222 if (!$force && !MARCUpdate::CanImport($resource, $current)) return FALSE;
223 if (!$resource->Verify()) return FALSE;
224 return TRUE;
225 }
226 protected function ResourceImported($resource) {
227 foreach ($this->_importResourceCallbacks as $callback) call_user_func($callback, $this, $resource);
228 }
229 public function RegisterResourceFilterCallback($callback) {
230 $this->_importResourceFilterCallbacks[] = $callback;
231 }
232 public function RegisterResourceImportCallback($callback) {
233 $this->_importResourceCallbacks[] = $callback;
234 } 260 }
235 public function SyncHTTP($server, $options = array()) { 261 public function SyncHTTP($server, $options = array()) {
236 $log = isset($options['log']) ? $options['log'] : TRUE; 262 $log = isset($options['log']) ? $options['log'] : TRUE;
237 $result = array(); 263 $result = array();
238 $method = isset($options['method']) ? $options['method'] : 'PUT'; 264 $method = isset($options['method']) ? $options['method'] : 'PUT';