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 cms
23 */
24
25/**
26 * CMS SignedData implementation.
27 *
28 * <pre>
29 *  SignedData ::= SEQUENCE {
30 *        version               CMSVersion,
31 *        digestAlgorithms      DigestAlgorithmIdentifiers,
32 *        encapContentInfo      EncapsulatedContentInfo,
33 *        certificates      [0] IMPLICIT CertificateSet OPTIONAL,
34 *        crls              [1] IMPLICIT CertificateRevocationLists OPTIONAL,
35 *        signerInfos           SignerInfos
36 * }
37 * </pre>
38 *
39 * @package asn1
40 * @subpackage cms
41 *
42 * @link http://tools.ietf.org/html/rfc3852#section-5 RFC 3852: Cryptographic Message Syntax
43 */
44class CMSSignedData implements ASN1DEREncodable {
45
46    /**
47     * Object identifier for the Signed-Data content type.
48     */
49    const OID = "1.2.840.113549.1.7.2";
50
51    private $version;
52    private $digestAlgorithms;
53    private $encapsulatedContent;
54    private $certificates;
55    private $signerInfos;
56
57    /**
58     * Constructs a new instance of CMSSignedData.
59     *
60     */
61    public function __construct() {
62    }
63
64    /**
65     * Decodes the given ASN1Sequence as CMSSignedData.
66     *
67     * @throws GTException
68     * @param  ASN1Sequence $object CMSSignedData encoded as an ASN1Sequence.
69     * @return void
70     */
71    public function decode($object) {
72
73        if (!$object instanceof ASN1Sequence) {
74            throw new GTException("Expecting an ASN1Sequence");
75        }
76
77        $size = $object->getObjectCount();
78
79        if ($size < 4 || $size > 6) {
80            throw new GTException("Invalid sequence size: {$size}");
81        }
82
83        $version = $object->getObjectAt(0);
84
85        if (!$version instanceof ASN1Integer) {
86            throw new GTException("Expecting an ASN1Integer");
87        }
88
89        $this->version = (int) $version->getValue();
90
91        if ($this->version !== 3 && $this->version != 1) {
92            throw new GTException("Invalid version: {$this->version}");
93        }
94
95        $set = $object->getObjectAt(1);
96
97        if (!$set instanceof ASN1Set) {
98            throw new GTException("Expecting an ASN1Set");
99        }
100
101        $digestAlgorithms = array();
102
103        foreach ($set->getObjects() as $item) {
104
105            $digestAlgorithm = new X509AlgorithmIdentifier();
106            $digestAlgorithm->decode($item);
107
108            array_push($digestAlgorithms, $digestAlgorithm);
109        }
110
111        $this->digestAlgorithms = $digestAlgorithms;
112
113        $encapsulatedContent = new CMSEncapsulatedContentInfo();
114        $encapsulatedContent->decode($object->getObjectAt(2));
115
116        $this->encapsulatedContent = $encapsulatedContent;
117
118        for ($i = 3; $i < $object->getObjectCount(); $i++) {
119
120            $item = $object->getObjectAt($i);
121
122            if ($item instanceof ASN1Tag) {
123
124                switch ($item->getTagValue()) {
125
126                    case 0:
127                        // certificates
128
129                        $this->certificates = array();
130
131                        $set = $item->getObjectAs(ASN1_TAG_SET);
132
133                        if ($set->getObjectCount() == 0) {
134                            throw new GTException("Certificates size: 0");
135                        }
136
137                        foreach ($set->getObjects() as $certificate) {
138
139                            if (!$certificate instanceof ASN1Sequence) {
140                                throw new GTException("Certificate should be encoded as an ASN1Sequence");
141                            }
142
143                            if ($certificate->getObjectCount() == 0) {
144                                throw new GTException("Certificate content missing");
145                            }
146
147                            array_push($this->certificates, $certificate);
148
149                        }
150
151                        break;
152
153                    case 1:
154                        // certificate revocation list
155
156                        throw new GTException("Certificate revocation lists not implemented");
157
158                        break;
159
160                    default:
161                        throw new GTException("Unknown tag value: {$item->getTagValue()}");
162
163                }
164
165            } else if ($item instanceof ASN1Set) {
166
167                $this->signerInfos = array();
168
169                if ($item->getObjectCount() == 0) {
170                    throw new GTException("SignerInfos size: 0");
171                }
172
173                foreach ($item->getObjects() as $info) {
174
175                    $signerInfo = new CMSSignerInfo();
176                    $signerInfo->decode($info);
177
178                    array_push($this->signerInfos, $signerInfo);
179                }
180
181            } else {
182                throw new GTException("Unkonwn child of CMSSignedData: " . get_class($item));
183            }
184
185        }
186
187    }
188
189    /**
190     * Encodes this CMSSignedData using DER.
191     *
192     * @return array byte array that contains the DER encoding of this CMSSignedData
193     */
194    public function encodeDER() {
195
196        $sequence = new ASN1Sequence();
197        $sequence->add(new ASN1Integer((int) $this->version));
198
199        $digestAlgorithms = new ASN1Set();
200
201        foreach ($this->digestAlgorithms as $digestAlgorithm) {
202            $digestAlgorithms->add($digestAlgorithm);
203        }
204
205        $sequence->add($digestAlgorithms);
206        $sequence->add($this->encapsulatedContent);
207
208        if (!empty($this->certificates)) {
209
210            $certificates = new ASN1Set();
211
212            foreach ($this->certificates as $certificate) {
213                $certificates->add($certificate);
214            }
215
216            $certificatesTag = new ASN1Tag();
217            $certificatesTag->setExplicit(false);
218            $certificatesTag->setTagClass(ASN1_TAG_CONTEXT);
219            $certificatesTag->setTagType(ASN1_TAG_CONSTRUCTED);
220            $certificatesTag->setTagValue(0);
221            $certificatesTag->setObject($certificates);
222
223            $sequence->add($certificatesTag);
224        }
225
226        $signerInfos = new ASN1Set();
227
228        foreach ($this->signerInfos as $signerInfo) {
229            $signerInfos->add($signerInfo);
230        }
231
232        $sequence->add($signerInfos);
233
234        return $sequence->encodeDER();
235    }
236
237    /**
238     * Gets the digest algorithms.
239     *
240     * @return array array of X509AlgorithmIdentifer
241     */
242    public function getDigestAlgorithms() {
243        return $this->digestAlgorithms;
244    }
245
246    /**
247     * Gets the encapsulated content.
248     *
249     * @return CMSEncapsulatedContentInfo the encapsulated content
250     */
251    public function getEncapsulatedContent() {
252        return $this->encapsulatedContent;
253    }
254
255    /**
256     * Gets the certificates.
257     *
258     * @return array certificates as raw unparsed ASN1Sequence instances
259     */
260    public function getCertificates() {
261        return $this->certificates;
262    }
263
264    /**
265     * Sets the certificates.
266     *
267     * @param  array $certificates certufificates as raw unparsed ASN1Sequence instances
268     * @return void
269     */
270    public function setCertificates($certificates) {
271        $this->certificates = $certificates;
272    }
273
274    /**
275     * Gets the signer infos.
276     *
277     * @return array of CMSSignerInfo
278     */
279    public function getSignerInfos() {
280        return $this->signerInfos;
281    }
282
283    /**
284     * Convenience method for timestamps to get the single signer info.
285     *
286     *
287     * @return CMSSignerInfo the first signer info
288     */
289    public function getSignerInfo() {
290
291        if (isset($this->signerInfos[0])) {
292            return $this->signerInfos[0];
293        }
294
295        return null;
296    }
297
298    /**
299     * Gets the version.
300     *
301     * @return int version
302     */
303    public function getVersion() {
304        return $this->version;
305    }
306}
307
308?>
309