]> git.mxchange.org Git - friendica.git/blob - include/salmon.php
Merge pull request #2669 from annando/1607-new-probe
[friendica.git] / include / salmon.php
1 <?php
2
3 require_once('include/crypto.php');
4 require_once('include/Probe.php');
5
6 function get_salmon_key($uri,$keyhash) {
7         $ret = array();
8
9         logger('Fetching salmon key for '.$uri);
10
11         $arr = Probe::lrdd($uri);
12
13         if(is_array($arr)) {
14                 foreach($arr as $a) {
15                         if($a['@attributes']['rel'] === 'magic-public-key') {
16                                 $ret[] = $a['@attributes']['href'];
17                         }
18                 }
19         }
20         else {
21                 return '';
22         }
23
24         // We have found at least one key URL
25         // If it's inline, parse it - otherwise get the key
26
27         if(count($ret)) {
28                 for($x = 0; $x < count($ret); $x ++) {
29                         if(substr($ret[$x],0,5) === 'data:') {
30                                 if(strstr($ret[$x],','))
31                                         $ret[$x] = substr($ret[$x],strpos($ret[$x],',')+1);
32                                 else
33                                         $ret[$x] = substr($ret[$x],5);
34                         }
35                         else
36                                 $ret[$x] = fetch_url($ret[$x]);
37                 }
38         }
39
40
41         logger('Key located: ' . print_r($ret,true));
42
43         if(count($ret) == 1) {
44
45                 // We only found one one key so we don't care if the hash matches.
46                 // If it's the wrong key we'll find out soon enough because
47                 // message verification will fail. This also covers some older
48                 // software which don't supply a keyhash. As long as they only
49                 // have one key we'll be right.
50
51                 return $ret[0];
52         }
53         else {
54                 foreach($ret as $a) {
55                         $hash = base64url_encode(hash('sha256',$a));
56                         if($hash == $keyhash)
57                                 return $a;
58                 }
59         }
60
61         return '';
62 }
63
64
65
66 function slapper($owner,$url,$slap) {
67
68         // does contact have a salmon endpoint?
69
70         if(! strlen($url))
71                 return;
72
73
74         if(! $owner['sprvkey']) {
75                 logger(sprintf("user '%s' (%d) does not have a salmon private key. Send failed.",
76                 $owner['username'],$owner['uid']));
77                 return;
78         }
79
80         logger('slapper called for '.$url.'. Data: ' . $slap);
81
82         // create a magic envelope
83
84         $data      = base64url_encode($slap);
85         $data_type = 'application/atom+xml';
86         $encoding  = 'base64url';
87         $algorithm = 'RSA-SHA256';
88         $keyhash   = base64url_encode(hash('sha256',salmon_key($owner['spubkey'])),true);
89
90         // precomputed base64url encoding of data_type, encoding, algorithm concatenated with periods
91
92         $precomputed = '.YXBwbGljYXRpb24vYXRvbSt4bWw=.YmFzZTY0dXJs.UlNBLVNIQTI1Ng==';
93
94         $signature   = base64url_encode(rsa_sign(str_replace('=','',$data . $precomputed),$owner['sprvkey']));
95
96         $signature2  = base64url_encode(rsa_sign($data . $precomputed,$owner['sprvkey']));
97
98         $signature3  = base64url_encode(rsa_sign($data,$owner['sprvkey']));
99
100         $salmon_tpl = get_markup_template('magicsig.tpl');
101
102         $salmon = replace_macros($salmon_tpl,array(
103                 '$data'      => $data,
104                 '$encoding'  => $encoding,
105                 '$algorithm' => $algorithm,
106                 '$keyhash'   => $keyhash,
107                 '$signature' => $signature
108         ));
109
110         // slap them
111         post_url($url,$salmon, array(
112                 'Content-type: application/magic-envelope+xml',
113                 'Content-length: ' . strlen($salmon)
114         ));
115
116         $a = get_app();
117         $return_code = $a->get_curl_code();
118
119         // check for success, e.g. 2xx
120
121         if($return_code > 299) {
122
123                 logger('compliant salmon failed. Falling back to status.net hack2');
124
125                 // Entirely likely that their salmon implementation is
126                 // non-compliant. Let's try once more, this time only signing
127                 // the data, without stripping '=' chars
128
129                 $salmon = replace_macros($salmon_tpl,array(
130                         '$data'      => $data,
131                         '$encoding'  => $encoding,
132                         '$algorithm' => $algorithm,
133                         '$keyhash'   => $keyhash,
134                         '$signature' => $signature2
135                 ));
136
137                 // slap them
138                 post_url($url,$salmon, array(
139                         'Content-type: application/magic-envelope+xml',
140                         'Content-length: ' . strlen($salmon)
141                 ));
142                 $return_code = $a->get_curl_code();
143
144
145                 if($return_code > 299) {
146
147                         logger('compliant salmon failed. Falling back to status.net hack3');
148
149                         // Entirely likely that their salmon implementation is
150                         // non-compliant. Let's try once more, this time only signing
151                         // the data, without the precomputed blob
152
153                         $salmon = replace_macros($salmon_tpl,array(
154                                 '$data'      => $data,
155                                 '$encoding'  => $encoding,
156                                 '$algorithm' => $algorithm,
157                                 '$keyhash'   => $keyhash,
158                                 '$signature' => $signature3
159                         ));
160
161                         // slap them
162                         post_url($url,$salmon, array(
163                                 'Content-type: application/magic-envelope+xml',
164                                 'Content-length: ' . strlen($salmon)
165                         ));
166                         $return_code = $a->get_curl_code();
167                 }
168         }
169         logger('slapper for '.$url.' returned ' . $return_code);
170         if(! $return_code)
171                 return(-1);
172         if(($return_code == 503) && (stristr($a->get_curl_headers(),'retry-after')))
173                 return(-1);
174
175         return ((($return_code >= 200) && ($return_code < 300)) ? 0 : 1);
176 }
177