1<?php
2/*
3 * Copyright 2008-2010 GuardTime AS
4 *
5 * This file is part of the GuardTime PHP SDK.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *     http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19
20/**
21 * @package asn1
22 * @subpackage x509
23 */
24
25/**
26 * X.509 Certificate implementation.
27 *
28 * This class treats X.509 certificates as BLOBs and doesn't
29 * actually fully implement ASN.1 decoding/encoding of X509Certificates.
30 *
31 * Wrapper methods are provided for PHP's openssl_xxx functions.
32 *
33 * @package asn1
34 * @subpackage x509
35 */
36class X509Certificate {
37
38    private $bytes;
39
40    private $cert;
41    private $data;
42    private $pkey;
43
44    /**
45     * Constructs a new instance of X509Certificate.
46     *
47     * @throws GTException
48     * @param  array|ASN1DEREncodable $data
49     * @return void
50     */
51    public function __construct($data) {
52
53        if (is_array($data)) {
54            $this->bytes = $data;
55
56        } else if ($data instanceof ASN1DEREncodable) {
57            $this->bytes = $data->encodeDER();
58
59        } else {
60            throw new GTException("paramater data must be an array of bytes or an instance of ASN1DEREncodable");
61
62        }
63    }
64
65    /**
66     * Destructs this X509Certificate.
67     *
68     * This method frees any allocated OpenSSL resources.
69     *
70     */
71    public function __destruct() {
72
73        if ($this->pkey !== null && $this->pkey !== false) {
74            openssl_pkey_free($this->pkey);
75        }
76
77        if ($this->cert !== null && $this->cert !== false) {
78            openssl_x509_free($this->cert);
79        }
80
81    }
82
83    /**
84     * PEM encodes this certificate.
85     *
86     * @return string pem encoded certificate
87     */
88    public function encodePEM() {
89
90        $pem = "";
91        $pem .= "-----BEGIN CERTIFICATE-----\r\n";
92
93        $body = GTBase64::encode($this->bytes);
94        $body = wordwrap($body, 64, "\r\n", true);
95
96        if ($body{strlen($body) - 1} != "\n") {
97            $body .= "\r\n";
98        }
99
100        $pem .= $body;
101        $pem .= "-----END CERTIFICATE-----\r\n";
102
103        return $pem;
104
105    }
106
107    /**
108     * Verifies the given signature using this certificate's public key.
109     *
110     * @param  array $data byte array containing the data bytes that were signed
111     * @param  array $sign byte array containing the signature bytes
112     * @param  string $algorithm the hash algorithm to use
113     * @return bool true if the signature is valid
114     */
115    public function verifySignature($data, $sign, $algorithm = 'sha256') {
116
117        if (empty($data)) {
118            throw new GTException("Parameter data is required");
119        }
120
121        if (empty($sign)) {
122            throw new GTException("Parameter sign is required");
123        }
124
125        if ($this->cert === null || $this->cert === false) {
126            $this->cert = openssl_x509_read($this->encodePEM());
127        }
128
129        if ($this->pkey == null || $this->pkey === false) {
130            $this->pkey = openssl_pkey_get_public($this->cert);
131        }
132
133        return X509Certificate::verifyPublicKeySignature($this->pkey, $data, $sign, $algorithm);
134
135    }
136
137    /**
138     * Gets the certificate parameters.
139     *
140     * @return array containing the certificate parameters
141     * @see openssl_x509_parse
142     */
143    public function getParameters() {
144
145        if ($this->cert === null || $this->cert === false) {
146            $this->cert = openssl_x509_read($this->encodePEM());
147        }
148
149        if ($this->data === null) {
150            $this->data = openssl_x509_parse($this->cert);
151        }
152
153        return $this->data;
154
155    }
156
157    /**
158     * Gets this certificate's public key.
159     *
160     * @return resource OpenSSL public key resource
161     * @see openssl_pkey_get_public
162     *
163     */
164    public function getPublicKey() {
165
166        if ($this->cert === null || $this->cert === false) {
167            $this->cert = openssl_x509_read($this->encodePEM());
168        }
169
170        if ($this->pkey === null || $this->pkey === false) {
171            $this->pkey = openssl_pkey_get_public($this->cert);
172        }
173
174        return $this->pkey;
175    }
176
177    /**
178     * Checks if this certificate is valid for given purpose.
179     *
180     * @throws GTException
181     * @param  int $purpose purpose
182     * @param  array $chain certificate chain
183     * @param  array $cainfo root certificates
184     * @return bool true if this certificate is valid
185     *
186     * @see openssl_x509_checkpurpose
187     */
188    public function isValid($purpose, $chain = array(), $cainfo = array()) {
189
190        if ($this->cert === null || $this->cert === false) {
191            $this->cert = openssl_x509_parse($this->encodePEM());
192        }
193
194        // $untrustedFile = tempnam(sys_get_temp_dir(), "guardtime_ca");
195        $untrustedFile = tempnam(DOKU_INC.'/data/tmp/', "guardtime_ca");
196
197
198        foreach ($chain as $cert) {
199            GTUtil::write($untrustedFile, GTUtil::toByteArray($cert->encodePEM()));
200        }
201
202        if ($cainfo == null) {
203            $cainfo = array();
204
205        } else if (is_string($cainfo)) {
206
207            if (!is_file($cainfo)) {
208                throw new GTException("Specified cainfo file does not exist: {$cainfo}");
209            }
210
211            $cainfo = array($cainfo);
212
213        } else {
214            throw new GTException("Invalid cainfo specified: " . $cainfo);
215
216        }
217
218        $result = openssl_x509_checkpurpose($this->cert, $purpose, $cainfo, $untrustedFile) === true;
219
220        unlink($untrustedFile);
221
222        return $result;
223
224    }
225
226    /**
227     * Gets the public key hash for given public key.
228     *
229     * @static
230     * @throws GTException
231     * @param  resource $pkey OpenSSL public key resource
232     * @return GTDataHash public key hash
233     */
234    public static function getPublicKeyHash($pkey) {
235
236        if (!is_resource($pkey)) {
237            throw new GTException("Parameter pkey must be a valid resource of type OpenSSL key");
238        }
239
240        $params = openssl_pkey_get_details($pkey);
241
242        $string = $params["key"];
243        $string = str_replace("-----BEGIN PUBLIC KEY-----", "", $string);
244        $string = str_replace("-----END PUBLIC KEY-----", "", $string);
245        $string = str_replace("\r", "", $string);
246        $string = str_replace("\n", "", $string);
247
248        $bytes = GTBase64::decode($string);
249
250        $hash = new GTDataHash(GTHashAlgorithm::getByName('SHA256'));
251        $hash->update($bytes);
252        $hash->close();
253
254        return $hash;
255    }
256
257
258    /**
259     * Verifies public key signature.
260     *
261     * @static
262     * @throws GTException
263     * @param  resource $pkey public key to use for verification
264     * @param  array $data array of data bytes
265     * @param  array $sign array of signature bytes
266     * @param  string $algorithm the hash algorithm to use
267     * @return bool true if signature is valid
268     */
269    public static function verifyPublicKeySignature($pkey, $data, $sign, $algorithm = 'sha256') {
270
271        if (empty($data)) {
272            throw new GTException("Parameter data is required");
273        }
274
275        if (empty($sign)) {
276            throw new GTException("Parameter sign is required");
277        }
278
279        if (!is_resource($pkey)) {
280            throw new GTException("Parameter pkey must be a valid resource of type OpenSSL key");
281        }
282
283        $data = GTUtil::fromByteArray($data);
284        $sign = GTUtil::fromByteArray($sign);
285
286        return openssl_verify($data, $sign, $pkey, $algorithm) === 1;
287
288    }
289}
290
291?>
292