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