1 /**
2 * OpenPGP Codec
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.codec.openpgp;
12 
13 import botan.filters.filters;
14 import botan.filters.basefilt;
15 import botan.filters.b64_filt;
16 import botan.utils.charset;
17 import botan.checksum.crc24;
18 import botan.filters.data_src;
19 import std.array : Appender;
20 import memutils.hashmap;
21 import botan.utils.types;
22 
23 /**
24 * Params:
25 *  input = the input data
26 *  length = length of input in bytes
27 *  label = the human-readable label
28 *  headers = a set of key/value pairs included in the header
29 */
30 string PGP_encode(const(ubyte)* input, size_t length, in string label,
31                   in HashMapRef!(string, string) headers)
32 {
33     const string PGP_HEADER = "-----BEGIN PGP " ~ label ~ "-----";
34     const string PGP_TRAILER = "-----END PGP " ~ label ~ "-----";
35     __gshared immutable size_t PGP_WIDTH = 64;
36     
37     Appender!string pgp_encoded = PGP_HEADER;
38     
39     if (headers.get("Version") != null)
40         pgp_encoded ~= "Version: " ~ headers["Version"] ~ '\n';
41 
42     foreach(const ref string k, const ref string v; headers)
43     {
44         if (k != "Version")
45             pgp_encoded ~= k ~ ": " ~ v ~ '\n';
46     }
47     pgp_encoded ~= '\n';
48     
49     Pipe pipe = Pipe(new Fork(
50         new Base64Encoder(true, PGP_WIDTH),
51         new Chain(new HashFilter(new CRC24), new Base64Encoder)
52         )
53     );
54     
55     pipe.processMsg(input, length);
56     
57     pgp_encoded ~= pipe.toString(0);
58     pgp_encoded ~= '=' ~ pipe.toString(1) ~ '\n';
59     pgp_encoded ~= PGP_TRAILER;
60     
61     return pgp_encoded.data;
62 }
63 
64 /**
65 * Params:
66 *  input = the input data
67 *  length = length of input in bytes
68 *  type = the human-readable label
69 */
70 string PGP_encode(const(ubyte)* input, size_t length, in string type)
71 {
72     HashMapRef!(string, string) empty;
73     return PGP_encode(input, length, type, empty);
74 }
75 
76 /**
77 * Params:
78 *  source = the input source
79 *  label = is set to the human-readable label
80 *  headers = is set to any headers
81 * Returns: decoded output as raw binary
82 */
83 SecureVector!ubyte PGP_decode(DataSource source,
84                             ref string label,
85                             ref HashMapRef!(string, string) headers)
86 {
87     __gshared immutable size_t RANDOM_CHAR_LIMIT = 5;
88     
89     const string PGP_HEADER1 = "-----BEGIN PGP ";
90     const string PGP_HEADER2 = "-----";
91     size_t position = 0;
92     
93     while (position != PGP_HEADER1.length)
94     {
95         ubyte b;
96         if (!source.readByte(b))
97             throw new DecodingError("PGP: No PGP header found");
98         if (b == PGP_HEADER1[position])
99             ++position;
100         else if (position >= RANDOM_CHAR_LIMIT)
101             throw new DecodingError("PGP: Malformed PGP header");
102         else
103             position = 0;
104     }
105     position = 0;
106     Appender!string label_buf;
107     while (position != PGP_HEADER2.length)
108     {
109         ubyte b;
110         if (!source.readByte(b))
111             throw new DecodingError("PGP: No PGP header found");
112         if (b == PGP_HEADER2[position])
113             ++position;
114         else if (position)
115             throw new DecodingError("PGP: Malformed PGP header");
116         
117         if (position == 0)
118             label_buf ~= cast(char)(b);
119     }
120     label = label_buf.data;
121     headers.clear();
122     bool end_of_headers = false;
123     while (!end_of_headers)
124     {
125         string this_header;
126         ubyte b = 0;
127         while (b != '\n')
128         {
129             if (!source.readByte(b))
130                 throw new DecodingError("PGP: Bad armor header");
131             if (b != '\n')
132                 this_header ~= cast(char)(b);
133         }
134         
135         end_of_headers = true;
136         for (size_t j = 0; j != this_header.length; ++j)
137             if (!isSpace(this_header[j]))
138                 end_of_headers = false;
139         
140         if (!end_of_headers)
141         {
142             import std.algorithm : countUntil;
143             ptrdiff_t pos = this_header.countUntil(": ");
144             if (pos == -1)
145                 throw new DecodingError("OpenPGP: Bad headers");
146             
147             string key = this_header[0 .. pos];
148             string value = this_header[pos + 2 .. $];
149             headers[key] = value;
150         }
151     }
152     
153     Pipe base64 = Pipe(new Base64Decoder,
154                         new Fork(    null, 
155                              new Chain(new HashFilter(new CRC24),
156                               new Base64Encoder)
157                          )
158                        );
159 
160     base64.startMsg();
161     
162     const string PGP_TRAILER = "-----END PGP " ~ label ~ "-----";
163     position = 0;
164     bool newline_seen = 0;
165     Appender!string crc;
166     while (position != PGP_TRAILER.length)
167     {
168         ubyte b;
169         if (!source.readByte(b))
170             throw new DecodingError("PGP: No PGP trailer found");
171         if (b == PGP_TRAILER[position])
172             ++position;
173         else if (position)
174             throw new DecodingError("PGP: Malformed PGP trailer");
175         
176         if (b == '=' && newline_seen)
177         {
178             while (b != '\n')
179             {
180                 if (!source.readByte(b))
181                     throw new DecodingError("PGP: Bad CRC tail");
182                 if (b != '\n')
183                     crc ~= cast(char)(b);
184             }
185         }
186         else if (b == '\n')
187             newline_seen = true;
188         else if (position == 0)
189         {
190             base64.write(b);
191             newline_seen = false;
192         }
193     }
194     base64.endMsg();
195     
196     if (crc.data.length > 0 && crc.data != base64.toString(1))
197         throw new DecodingError("PGP: Corrupt CRC");
198     
199     return base64.readAll();
200 }
201 
202 /**
203 * Params:
204 *  source = the input source
205 *  label = is set to the human-readable label
206 * Returns: decoded output as raw binary
207 */
208 SecureVector!ubyte PGP_decode(DataSource source, ref string label)
209 {
210     HashMapRef!(string, string) ignored;
211     return PGP_decode(source, label, ignored);
212 }