]> git.mxchange.org Git - friendica.git/blob - include/salmon.php
merged multipart email changes
[friendica.git] / include / salmon.php
1 <?php
2
3 require_once('library/asn1.php');
4
5 function salmon_key($pubkey) {
6         $lines = explode("\n",$pubkey);
7         unset($lines[0]);
8         unset($lines[count($lines)]);
9         $x = base64_decode(implode('',$lines));
10
11         $r = ASN_BASE::parseASNString($x);
12
13         $m = $r[0]->asnData[1]->asnData[0]->asnData[0]->asnData;
14         $e = $r[0]->asnData[1]->asnData[0]->asnData[1]->asnData;
15
16
17         return 'RSA' . '.' . $m . '.' . $e ;
18 }
19
20
21 function base64url_encode($s, $strip_padding = false) {
22
23         $s = strtr(base64_encode($s),'+/','-_');
24
25         if($strip_padding)
26                 $s = str_replace('=','',$s);
27
28         return $s;
29 }
30
31 function base64url_decode($s) {
32
33 /*
34  *  // Placeholder for new rev of salmon which strips base64 padding.
35  *  // PHP base64_decode handles the un-padded input without requiring this step
36  *  // Uncomment if you find you need it.
37  *
38  *      $l = strlen($s);
39  *      if(! strpos($s,'=')) {
40  *              $m = $l % 4;
41  *              if($m == 2)
42  *                      $s .= '==';
43  *              if($m == 3)
44  *                      $s .= '=';
45  *      }
46  *
47  */
48
49         return base64_decode(strtr($s,'-_','+/'));
50 }
51
52 function get_salmon_key($uri,$keyhash) {
53         $ret = array();
54
55         logger('Fetching salmon key');
56
57         $arr = lrdd($uri);
58
59         if(is_array($arr)) {
60                 foreach($arr as $a) {
61                         if($a['@attributes']['rel'] === 'magic-public-key') {
62                                 $ret[] = $a['@attributes']['href'];
63                         }
64                 }
65         }
66         else {
67                 return '';
68         }
69
70         // We have found at least one key URL
71         // If it's inline, parse it - otherwise get the key
72
73         if(count($ret)) {
74                 for($x = 0; $x < count($ret); $x ++) {
75                         if(substr($ret[$x],0,5) === 'data:') {
76                                 if(strstr($ret[$x],','))
77                                         $ret[$x] = substr($ret[$x],strpos($ret[$x],',')+1);
78                                 else
79                                         $ret[$x] = substr($ret[$x],5);
80                         }
81                         else
82                                 $ret[$x] = fetch_url($ret[$x]);
83                 }
84         }
85
86
87         logger('Key located: ' . print_r($ret,true));
88
89         if(count($ret) == 1) {
90
91                 // We only found one one key so we don't care if the hash matches.
92                 // If it's the wrong key we'll find out soon enough because 
93                 // message verification will fail. This also covers some older 
94                 // software which don't supply a keyhash. As long as they only
95                 // have one key we'll be right. 
96
97                 return $ret[0];
98         }
99         else {
100                 foreach($ret as $a) {
101                         $hash = base64url_encode(hash('sha256',$a));
102                         if($hash == $keyhash)
103                                 return $a;
104                 }
105         }
106
107         return '';
108 }
109
110         
111                 
112 function slapper($owner,$url,$slap) {
113
114         logger('slapper called. Data: ' . $slap);
115
116         // does contact have a salmon endpoint? 
117
118         if(! strlen($url))
119                 return;
120
121         // add all namespaces to item
122
123 $namespaces = <<< EOT
124 <entry xmlns="http://www.w3.org/2005/Atom"
125       xmlns:thr="http://purl.org/syndication/thread/1.0"
126       xmlns:at="http://purl.org/atompub/tombstones/1.0"
127       xmlns:media="http://purl.org/syndication/atommedia"
128       xmlns:dfrn="http://purl.org/macgirvin/dfrn/1.0" 
129       xmlns:as="http://activitystrea.ms/spec/1.0/"
130       xmlns:georss="http://www.georss.org/georss" 
131       xmlns:poco="http://portablecontacts.net/spec/1.0" 
132       xmlns:ostatus="http://ostatus.org/schema/1.0" >
133 EOT;
134
135         $slap = str_replace('<entry>',$namespaces,$slap);
136         
137         // create a magic envelope
138
139         $data      = base64url_encode($slap);
140         $data_type = 'application/atom+xml';
141         $encoding  = 'base64url';
142         $algorithm = 'RSA-SHA256';
143         $keyhash   = base64url_encode(hash('sha256',salmon_key($owner['spubkey'])));
144
145         // Setup RSA stuff to PKCS#1 sign the data
146
147         set_include_path(get_include_path() . PATH_SEPARATOR . 'phpsec');
148
149         require_once('phpsec/Crypt/RSA.php');
150
151     $rsa = new CRYPT_RSA();
152     $rsa->signatureMode = CRYPT_RSA_SIGNATURE_PKCS1;
153     $rsa->setHash('sha256');
154         $rsa->loadKey($owner['sprvkey']);
155
156         // precomputed base64url encoding of data_type, encoding, algorithm concatenated with periods
157
158         $precomputed = '.YXBwbGljYXRpb24vYXRvbSt4bWw=.YmFzZTY0dXJs.UlNBLVNIQTI1Ng==';
159
160         $signature  = base64url_encode($rsa->sign($data . $precomputed));
161
162         $signature2  = base64url_encode($rsa->sign($data));
163
164         $salmon_tpl = get_markup_template('magicsig.tpl');
165         $salmon = replace_macros($salmon_tpl,array(
166                 '$data'      => $data,
167                 '$encoding'  => $encoding,
168                 '$algorithm' => $algorithm,
169                 '$keyhash'   => $keyhash,
170                 '$signature' => $signature
171         ));
172
173         // slap them 
174         post_url($url,$salmon, array(
175                 'Content-type: application/magic-envelope+xml',
176                 'Content-length: ' . strlen($salmon)
177         ));
178
179         $a = get_app();
180         $return_code = $a->get_curl_code();
181
182         // check for success, e.g. 2xx
183
184         if($return_code > 299) {
185
186                 logger('slapper: compliant salmon failed. Falling back to status.net hack');
187
188                 // Entirely likely that their salmon implementation is
189                 // non-compliant. Let's try once more, this time only signing
190                 // the data, without the precomputed blob 
191
192                 $salmon = replace_macros($salmon_tpl,array(
193                         '$data'      => $data,
194                         '$encoding'  => $encoding,
195                         '$algorithm' => $algorithm,
196                         '$keyhash'   => $keyhash,
197                         '$signature' => $signature2
198                 ));
199
200                 // slap them 
201                 post_url($url,$salmon, array(
202                         'Content-type: application/magic-envelope+xml',
203                         'Content-length: ' . strlen($salmon)
204                 ));
205                 $return_code = $a->get_curl_code();
206
207         }
208         logger('slapper returned ' . $return_code); 
209         if(! $return_code)
210                 return(-1);
211         return ((($return_code >= 200) && ($return_code < 300)) ? 0 : 1);
212 }
213