1 /**
2 * X.509 SIGNED Object
3 * 
4 * Copyright:
5 * (C) 1999-2007 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.cert.x509.x509_obj;
12 
13 import botan.constants;
14 import botan.asn1.asn1_obj;
15 import botan.filters.pipe;
16 import botan.rng.rng;
17 import botan.pubkey.x509_key;
18 import botan.pubkey.pubkey;
19 import botan.asn1.oids;
20 import botan.asn1.der_enc;
21 import botan.asn1.ber_dec;
22 import botan.utils.parsing;
23 import botan.codec.pem;
24 import std.algorithm;
25 import botan.utils.types;
26 import botan.utils.types;
27 import memutils.hashmap;
28 
29 version(X509):
30 
31 /**
32 * This class represents abstract X.509 signed objects as
33 * in the X.500 SIGNED macro
34 */
35 class X509Object : ASN1Object
36 {
37 public:
38     /**
39     * The underlying data that is to be or was signed
40     * Returns: data that is or was signed
41     */
42     final const(Vector!ubyte) tbsData() const
43     {
44         return putInSequence(m_tbs_bits);
45     }
46 
47     /**
48     * Returns: signature on tbsData()
49     */
50     final ref const(Vector!ubyte) signature() const
51     {
52         return m_sig;
53     }
54 
55     /**
56     * Returns: signature algorithm that was used to generate signature
57     */
58     final const(AlgorithmIdentifier) signatureAlgorithm() const
59     {
60         return m_sig_algo;
61     }
62 
63     /**
64     * Returns: hash algorithm that was used to generate signature
65     */
66     final string hashUsedForSignature() const
67     {
68         Vector!string sig_info = botan.utils.parsing.splitter(OIDS.lookup(m_sig_algo.oid), '/');
69         
70         if (sig_info.length != 2)
71             throw new InternalError("Invalid name format found for " ~ m_sig_algo.oid.toString());
72         
73         Vector!string pad_and_hash = parseAlgorithmName(sig_info[1]);
74         
75         if (pad_and_hash.length != 2)
76             throw new InternalError("Invalid name format " ~ sig_info[1]);
77         
78         return pad_and_hash[1];
79     }
80 
81 
82     /**
83     * Create a signed X509 object.
84     *
85     * Params:
86     *  signer = the signer used to sign the object
87     *  rng = the random number generator to use
88     *  algo= the algorithm identifier of the signature scheme
89     *  tbs_bits = the tbs bits to be signed
90     * Returns: signed X509 object
91     */
92     static Vector!ubyte makeSigned(ALLOC)(ref PKSigner signer,
93                                                RandomNumberGenerator rng,
94                                               in AlgorithmIdentifier algo,
95                                               auto const ref Vector!(ubyte, ALLOC) tbs_bits)
96     {
97         return DEREncoder()
98                 .startCons(ASN1Tag.SEQUENCE)
99                 .rawBytes(tbs_bits)
100                 .encode(algo)
101                 .encode(signer.signMessage(tbs_bits, rng), ASN1Tag.BIT_STRING)
102                 .endCons()
103                 .getContentsUnlocked();
104     }
105 
106     /// ditto
107     static Vector!ubyte makeSigned(ALLOC)(ref PKSigner signer,
108                                           RandomNumberGenerator rng,
109                                           in AlgorithmIdentifier algo,
110                                           auto const ref RefCounted!(Vector!(ubyte, ALLOC), ALLOC) tbs_bits)
111     {
112         return makeSigned(signer, rng, algo, *tbs_bits);
113     }
114 
115 
116     /**
117     * Check the signature on this data
118     * Params:
119     *  pub_key = the public key purportedly used to sign this data
120     * Returns: true if the signature is valid, otherwise false
121     */
122     final bool checkSignature(in PublicKey pub_key) const
123     {
124         assert(pub_key);
125         try {
126             Vector!string sig_info = botan.utils.parsing.splitter(OIDS.lookup(m_sig_algo.oid), '/');
127             
128             if (sig_info.length != 2 || sig_info[0] != pub_key.algoName)
129                 return false;
130             
131             string padding = sig_info[1];
132             SignatureFormat format = (pub_key.messageParts() >= 2) ? DER_SEQUENCE : IEEE_1363;
133             PKVerifier verifier = PKVerifier(pub_key, padding, format);
134             auto tbs = tbsData();
135             auto sig = signature().dup;
136             return verifier.verifyMessage(tbs, sig);
137         }
138         catch(Exception e)
139         {
140             return false;
141         }
142     }
143 
144     override void encodeInto(ref DEREncoder to) const
145     {
146         to.startCons(ASN1Tag.SEQUENCE)
147                 .startCons(ASN1Tag.SEQUENCE)
148                 .rawBytes(m_tbs_bits)
149                 .endCons()
150                 .encode(m_sig_algo)
151                 .encode(m_sig, ASN1Tag.BIT_STRING)
152                 .endCons();
153     }
154 
155     /*
156     * Read a BER encoded X.509 object
157     */
158     override void decodeFrom(ref BERDecoder from)
159     {
160         //logTrace("decodeFrom X509Object");
161         from.startCons(ASN1Tag.SEQUENCE)
162                 .startCons(ASN1Tag.SEQUENCE)
163                 .rawBytes(m_tbs_bits)
164                 .endCons()
165                 .decode(m_sig_algo)
166                 .decode(m_sig, ASN1Tag.BIT_STRING)
167                 .verifyEnd()
168                 .endCons();
169     }
170 
171 
172     /**
173     * Returns: BER encoding of this
174     */
175     final Vector!ubyte BER_encode() const
176     {
177 		static HashMap!(void*, ubyte[]) cache;
178 		if ((cast(void*)this) in cache) return Vector!ubyte(cache[cast(void*)this]);
179 		
180 		auto der = DEREncoder();
181 		encodeInto(der);
182 		auto ret = der.getContentsUnlocked();
183 		if (cache.length > 50)
184 			cache = HashMap!(void*, ubyte[])();
185 		cache[cast(void*)this] = ret[].dup;
186 
187 		return ret.move;
188     }
189 
190 
191     /**
192     * Returns: PEM encoding of this
193     */
194     final string PEM_encode() const
195     {
196         return PEM.encode(BER_encode(), m_PEM_label_pref);
197     }
198 
199     ~this() {}
200 protected:
201     /*
202     * Create a generic X.509 object
203     */
204     this(DataSource stream, in string labels)
205     {
206         init(stream, labels);
207     }
208 
209     /*
210     * Create a generic X.509 object
211     */
212     this(in string file, in string labels)
213     {
214         DataSource stream = cast(DataSource)DataSourceStream(file, true);
215         init(stream, labels);
216     }
217 
218     /*
219     * Create a generic X.509 object
220     */
221     this(ALLOC)(auto const ref Vector!(ubyte, ALLOC) vec, in string labels)
222     {
223         auto stream = DataSourceMemory(vec.ptr, vec.length);
224         init(cast(DataSource)stream, labels);
225     }
226 
227     /*
228     * Create a generic X.509 object
229     */
230     this(ALLOC)(auto const ref RefCounted!(Vector!(ubyte, ALLOC), ALLOC) vec, in string labels)
231     {
232         auto stream = DataSourceMemory(vec.ptr, vec.length);
233         init(cast(DataSource)stream, labels);
234     }
235 
236     /*
237     * Try to decode the actual information
238     */
239     final void doDecode()
240     {
241         try {
242             forceDecode();
243         }
244         catch(DecodingError e)
245         {
246             throw new DecodingError(m_PEM_label_pref ~ " decoding failed (" ~ e.msg ~ ")");
247         }
248         catch(InvalidArgument e)
249         {
250             throw new DecodingError(m_PEM_label_pref ~ " decoding failed (" ~ e.msg ~ ")");
251         }
252     }
253     this() { }
254     AlgorithmIdentifier m_sig_algo;
255     Vector!ubyte m_tbs_bits, m_sig;
256 
257 protected:
258     abstract void forceDecode();
259 
260 private:
261     /*
262     * Read a PEM or BER X.509 object
263     */
264     final void init(DataSource input, in string labels)
265     {
266         m_sig_algo = AlgorithmIdentifier();
267         m_PEM_labels_allowed = botan.utils.parsing.splitter(labels, '/');
268         if (m_PEM_labels_allowed.length < 1)
269             throw new InvalidArgument("Bad labels argument to X509Object");
270         
271         //logTrace("Initialize PEM/BER X.509 Object");
272         m_PEM_label_pref = m_PEM_labels_allowed[0];
273         
274         try {
275             if (maybeBER(input) && !PEM.matches(input))
276             {
277                 auto dec = BERDecoder(input);
278                 decodeFrom(dec);
279             }
280             else
281             {
282                 string got_label;
283                 auto ber = DataSourceMemory(PEM.decode(input, got_label));
284                 if (!m_PEM_labels_allowed[].canFind(got_label)) {
285                     throw new DecodingError("Invalid PEM Certificate format " ~ got_label ~ ", please convert it to a PEM or BER X.509 object format. Possible labels: " ~ m_PEM_labels_allowed[].to!string);
286 				}
287                 auto dec = BERDecoder(cast(DataSource)ber);
288                 decodeFrom(dec);
289             }
290         }
291         catch(DecodingError e)
292         {
293             throw new DecodingError(m_PEM_label_pref ~ " decoding failed: " ~ e.msg);
294         }
295     }
296 
297     Vector!string m_PEM_labels_allowed;
298     string m_PEM_label_pref;
299 }