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 }