File: Services_Signing_Base.php

package info (click to toggle)
spotweb 20130826%2Bdfsg2-2
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 5,132 kB
  • ctags: 11,281
  • sloc: php: 31,367; xml: 1,009; sh: 148; makefile: 83
file content (217 lines) | stat: -rw-r--r-- 6,535 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
<?php
require_once "Crypt/RSA.php";

abstract class Services_Signing_Base {
	/* 
	 * We never want to create this directly
	 */	
	private function __construct() {
	} # ctor

	/*
	 * Create a factory method
	 */
	static public function newServiceSigning() {
		/*
		 * Automatically select OpenSSL if
		 * possible
		 */
		if (!defined('CRYPT_RSA_MODE')) {
			if (extension_loaded("openssl")) {
				define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_OPENSSL);
			} else {
				define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL);
 			} # else
		} # if not defined


		if (CRYPT_RSA_MODE == CRYPT_RSA_MODE_OPENSSL) {
			return new Services_Signing_Openssl(); 
		} else {
			return new Services_Signing_Php(); 
		} # else
	} # newServiceSigning

	/*
	 * Actually validates the RSA signature
	 */
	abstract protected function checkRsaSignature($toCheck, $signature, $rsaKey, $useCache);

	/*
	 * Creates a public and private key
	 */
	abstract public function createPrivateKey($sslCnfPath);
	
	/*
	 * RSA signs a message and returns all possible values needed
	 * to validate:
	 *
	 * - base64 encoded signature (signature)
	 * - Public key (publickey)
	 * - message which is signed (message)
	 */
	public function signMessage($privatekey, $message) {
		/**
		 * Test code:
		 * 
		 * $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
		 * extract($rsa->createKey());
		 * $spotSigning = new SpotSigning();
		 * $x = $spotSigning->signMessage($privatekey, 'testmessage');
		 * var_dump($x);
		 * var_dump($spotSigning->checkRsaSignature('testmessage', $x['signature'], $x['publickey'], false));
		 *
		 */
		if (empty($privatekey)) {
			throw new InvalidPrivateKeyException();
		} # if
		 
		$rsa = new Crypt_RSA();
		$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
		$rsa->loadKey($privatekey);

		# extract de public key
		$signature = $rsa->sign($message);
		$publickey = $rsa->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_RAW);

		return array('signature' => base64_encode($signature),
					 'publickey' => array('modulo' => base64_encode($publickey['n']->toBytes()), 'exponent' => base64_encode($publickey['e']->toBytes())),
					 'message' => $message);
	} # signMessage

	/*
	 * Returns a public key
	 */
	function getPublicKey($privateKey) {
		$rsa = new Crypt_RSA();
		$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
		$rsa->loadKey($privateKey);

		# extract the public key
		$publicKey = $rsa->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_RAW);

		return array('publickey' => array('modulo' => base64_encode($publicKey['n']->toBytes()), 'exponent' => base64_encode($publicKey['e']->toBytes())));
	} # getPublicKey

	/*
	 * Convets a usuable public key for us, to a public key
	 * usable for the SpotNet native client (.NET format)
	 */
	public function pubkeyToXml($pubkey) {
		return "<RSAKeyValue><Modulus>" . $pubkey['modulo'] . '</Modulus><Exponent>' . $pubkey['exponent'] . '</Exponent></RSAKeyValue>';
	} # pubkeyToXml 
		

	/*
	 * Helper function to verify a spot header
	 */	
	public function verifySpotHeader($spot, $signature, $rsaKeys) {
		# This is the string to verify
		$toCheck = $spot['title'] . substr($spot['header'], 0, strlen($spot['header']) - strlen($spot['headersign']) - 1) . $spot['poster'];

		# Check the RSA signature on the spot
		return $this->checkRsaSignature($toCheck, $signature, $rsaKeys[$spot['keyid']], true);
	} # verifySpotHeader()

	/*
	 * Helper function which verifies a fullspot
	 */
	public function verifyFullSpot($spot) {
		if ((empty($spot['user-signature'])) || (empty($spot['user-key']))) {
			return false;
		} # if
		
		$verified = $this->checkRsaSignature('<' . $spot['messageid'] . '>', $spot['user-signature'], $spot['user-key'], false);
		if ((!$verified) && (!empty($spot['xml-signature']))) {
			$verified = $this->checkRsaSignature($spot['xml-signature'], $spot['user-signature'], $spot['user-key'], false);
		} # if
		
		return $verified;
	} # verifyFullSpot()

	/*
	 * Helper function to verify a comment header
	 */	
	public function verifyComment($comment) {
		$verified = false;

		if ((!empty($comment['user-signature'])) && (!empty($comment['user-key']))) {
			$verified = $this->checkRsaSignature('<' . $comment['messageid'] .  '>', $comment['user-signature'], $comment['user-key'], false);
			if (!$verified) {
				$verified = $this->checkRsaSignature('<' . $comment['messageid'] .  '>' . 
																implode("\r\n", $comment['body']) . "\r\n" . 
																$comment['fromhdr'], 
													$comment['user-signature'], 
													$comment['user-key'],
													false);
			} # if
		} # if

		/*
		 * When a spot is valid with regards to an RSA signature, we can also check the users'
		 * hash, which also should validate. This hash is a so-called hashcash and is only
		 * meant to require CPU power on the posting clinet preventing floods.
		 *
		 * Currently, some buggy clients post invalid hash cashes but valid spots so we cannot
		 * use this yet.
		 */
		if ($verified) {
			# $userSignedHash = sha1('<' . $comment['messageid'] . '>', false);
			# $verified = (substr($userSignedHash, 0, 4) == '0000');
		} # if

		return $verified;
	} # verifyComment()
	
	/*
	 * Calculates an SHA1 hash of a message until the first bytes match 0000. Please use
	 * the JavaScript variant for this
	 */
	function makeExpensiveHash($prefix, $suffix) {
		$runCount = 0;
		
		$hash = $prefix . $suffix;

		while(substr($hash, 0, 4) !== '0000') {	
			if ($runCount > 400000) {
				throw new Exception("Unable to calculate SHA1 hash: " . $runCount);
			} # if
			$runCount++;
			
			$uniquePart = $this->makeRandomStr(15);
			
			$hash = sha1($prefix . $uniquePart . $suffix, false);			
		} # while
		
		return $prefix . $uniquePart . $suffix;
	} # makeExpensiveHash

	/*
	 * Creates a random string of $len length with A-z0-9
	 */
	function makeRandomStr($len) {
		$possibleChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
		
		$unique = '';
		for($i = 0; $i < $len; $i++) {
			$unique .= $possibleChars[mt_rand(0, strlen($possibleChars) - 1)];
		} # for
		
		return $unique;
	} # makeRandomStr

	/*
	 * Calculates the user id using hte users' publickey
	 */		
	public function calculateSpotterId($userKey) {
		$userSignCrc = crc32(base64_decode($userKey));
		
		$userIdTmp = chr($userSignCrc & 0xFF) .
						chr(($userSignCrc >> 8) & 0xFF ).
						chr(($userSignCrc >> 16) & 0xFF) .
						chr(($userSignCrc >> 24) & 0xFF);
		
		return str_replace(array('/', '+', '='), '', base64_encode($userIdTmp));
	} # calculateSpotterId
	
} # Services_Signing_Base