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 }