void PEM_StripEncapsulatedBoundary(BufferedTransformation& bt, const SecByteBlock& pre, const SecByteBlock& post) { ByteQueue temp; SecByteBlock::const_iterator it; int n = 1, prePos = -1, postPos = -1; while(bt.AnyRetrievable() && n++) { SecByteBlock line, unused; PEM_ReadLine(bt, line, unused); // The write associated with an empty line must to occur. Otherwise, we loose the CR or LF // in an ecrypted private key between the control fields and the encapsulated text. //if(line.empty()) // continue; it = Search(line, pre); if(it != line.end()) { prePos = n; continue; } it = Search(line, post); if(it != line.end()) { postPos = n; continue; } PEM_WriteLine(temp, line); } if(prePos == -1) { string msg = "PEM_StripEncapsulatedBoundary: '"; msg += string((char*)pre.data(), pre.size()) + "' not found"; throw InvalidDataFormat(msg); } if(postPos == -1) { string msg = "PEM_StripEncapsulatedBoundary: '"; msg += string((char*)post.data(), post.size()) + "' not found"; throw InvalidDataFormat(msg); } if(prePos > postPos) throw InvalidDataFormat("PEM_StripEncapsulatedBoundary: header boundary follows footer boundary"); temp.TransferTo(bt); }
void PEM_NextObject(BufferedTransformation& src, BufferedTransformation& dest, bool trimTrailing) { if(!src.AnyRetrievable()) return; // We have four things to find: // 1. -----BEGIN (the leading begin) // 2. ----- (the trailing dashes) // 3. -----END (the leading end) // 4. ----- (the trailing dashes) // Once we parse something that purports to be PEM encoded, another routine // will have to look for something particular, like a RSA key. We *will* // inadvertently parse garbage, like -----BEGIN FOO BAR-----. It will // be caught later when a PEM_Load routine is called. static const size_t BAD_IDX = PEM_INVALID; // We use iterators for the search. However, an interator is invalidated // after each insert that grows the container. So we save indexes // from begin() to speed up searching. On each iteration, we simply // reinitialize them. SecByteBlock::const_iterator it; size_t idx1 = BAD_IDX, idx2 = BAD_IDX, idx3 = BAD_IDX, idx4 = BAD_IDX; // The idea is to read chunks in case there are multiple keys or // paramters in a BufferedTransformation. So we use CopyTo to // extract what we are interested in. We don't take anything // out of the BufferedTransformation (yet). // We also use indexes because the iterator will be invalidated // when we append to the ByteQueue. Even though the iterator // is invalid, `accum.begin() + index` will be valid. // Reading 8 or 10 lines at a time is an optimization from testing // against cacerts.pem. The file has 153 certs, so its a good test. // +2 to allow for CR + LF line endings. There's no guarantee a line // will be present, or it will be RFC1421_LINE_BREAK in size. static const size_t READ_SIZE = (RFC1421_LINE_BREAK + 1) * 10; static const size_t REWIND = max(SBB_PEM_BEGIN.size(), SBB_PEM_END.size()) + 2; SecByteBlock accum; size_t idx = 0, next = 0; size_t available = src.MaxRetrievable(); while(available) { // How much can we read? const size_t size = std::min(available, READ_SIZE); // Ideally, we would only scan the line we are reading. However, // we need to rewind a bit in case a token spans the previous // block and the block we are reading. But we can't rewind // into a previous index. Once we find an index, the variable // next is set to it. Hence the reason for the max() if(idx > REWIND) { const size_t x = idx - REWIND; next = max(next, x); } #if 0 // Next should be less than index by 10 or so std::cout << " Index: " << idx << std::endl; std::cout << " Next: " << next << std::endl; #endif // We need a temp queue to use CopyRangeTo. We have to use it // because there's no Peek that allows us to peek a range. ByteQueue tq; src.CopyRangeTo(tq, static_cast<lword>(idx), static_cast<lword>(size)); const size_t offset = accum.size(); accum.Grow(offset + size); tq.Get(accum.data() + offset, size); // Adjust sizes idx += size; available -= size; // Locate '-----BEGIN' if(idx1 == BAD_IDX) { it = search(accum.begin() + next, accum.end(), SBB_PEM_BEGIN.begin(), SBB_PEM_BEGIN.end()); if(it == accum.end()) continue; idx1 = it - accum.begin(); next = idx1 + SBB_PEM_BEGIN.size(); } // Locate '-----' if(idx2 == BAD_IDX && idx1 != BAD_IDX) { it = search(accum.begin() + next, accum.end(), SBB_PEM_TAIL.begin(), SBB_PEM_TAIL.end()); if(it == accum.end()) continue; idx2 = it - accum.begin(); next = idx2 + SBB_PEM_TAIL.size(); } // Locate '-----END' if(idx3 == BAD_IDX && idx2 != BAD_IDX) { it = search(accum.begin() + next, accum.end(), SBB_PEM_END.begin(), SBB_PEM_END.end()); if(it == accum.end()) continue; idx3 = it - accum.begin(); next = idx3 + SBB_PEM_END.size(); } // Locate '-----' if(idx4 == BAD_IDX && idx3 != BAD_IDX) { it = search(accum.begin() + next, accum.end(), SBB_PEM_TAIL.begin(), SBB_PEM_TAIL.end()); if(it == accum.end()) continue; idx4 = it - accum.begin(); next = idx4 + SBB_PEM_TAIL.size(); } } // Did we find `-----BEGIN XXX-----` (RFC 1421 calls this pre-encapsulated boundary)? if(idx1 == BAD_IDX || idx2 == BAD_IDX) throw InvalidDataFormat("PEM_NextObject: could not locate boundary header"); // Did we find `-----END XXX-----` (RFC 1421 calls this post-encapsulated boundary)? if(idx3 == BAD_IDX || idx4 == BAD_IDX) throw InvalidDataFormat("PEM_NextObject: could not locate boundary footer"); // *IF* the trailing '-----' occurred in the last 5 bytes in accum, then we might miss the // End of Line. We need to peek 2 more bytes if available and append them to accum. if(available >= 2) { ByteQueue tq; src.CopyRangeTo(tq, static_cast<lword>(idx), static_cast<lword>(2)); const size_t offset = accum.size(); accum.Grow(offset + 2); tq.Get(accum.data() + offset, 2); } else if(available == 1) { ByteQueue tq; src.CopyRangeTo(tq, static_cast<lword>(idx), static_cast<lword>(1)); const size_t offset = accum.size(); accum.Grow(offset + 1); tq.Get(accum.data() + offset, 1); } // Final book keeping const byte* ptr = accum.begin() + idx1; const size_t used = idx4 + SBB_PEM_TAIL.size(); const size_t len = used - idx1; // Include one CR/LF if its available in the accumulator next = idx1 + len; size_t adjust = 0; if(next < accum.size()) { byte c1 = accum[next]; byte c2 = 0; if(next + 1 < accum.size()) c2 = accum[next + 1]; // Longest match first if(c1 == '\r' && c2 == '\n') adjust = 2; else if(c1 == '\r' || c1 == '\n') adjust = 1; } dest.Put(ptr, len + adjust); dest.MessageEnd(); src.Skip(used + adjust); if(trimTrailing) { while (src.AnyRetrievable()) { byte b; src.Peek(b); if(!isspace(b)) break; src.Skip(1); } } }
void PEM_StripEncapsulatedHeader(BufferedTransformation& src, BufferedTransformation& dest, EncapsulatedHeader& header) { if(!src.AnyRetrievable()) return; SecByteBlock line, ending; size_t size = 0; // The first line *must* be Proc-Type. Ensure we read it before dropping into the loop. size = PEM_ReadLine(src, line, ending); if(size == 0 || line.empty()) throw InvalidDataFormat("PEM_StripEncapsulatedHeader: failed to locate Proc-Type"); SecByteBlock field = GetControlField(line); if(field.empty()) throw InvalidDataFormat("PEM_StripEncapsulatedHeader: failed to locate Proc-Type"); if(0 != CompareNoCase(field, SBB_PROC_TYPE)) throw InvalidDataFormat("PEM_StripEncapsulatedHeader: failed to locate Proc-Type"); line = GetControlFieldData(line); string tline(reinterpret_cast<const char*>(line.data()),line.size()); PEM_ParseVersion(tline, header.m_version); if(header.m_version != "4") throw NotImplemented("PEM_StripEncapsulatedHeader: encryption version " + header.m_version + " not supported"); PEM_ParseOperation(tline, header.m_operation); if(header.m_operation != "ENCRYPTED") throw NotImplemented("PEM_StripEncapsulatedHeader: operation " + header.m_operation + " not supported"); // Next, we have to read until the first empty line while(true) { if(!src.AnyRetrievable()) break; // End Of Buffer size = PEM_ReadLine(src, line, ending); if(size == 0) break; // End Of Buffer if(line.size() == 0) break; // size is non-zero; empty line field = GetControlField(line); if(0 == CompareNoCase(field, SBB_DEK_INFO)) { line = GetControlFieldData(line); tline = string(reinterpret_cast<const char*>(line.data()),line.size()); PEM_ParseAlgorithm(tline, header.m_algorithm); PEM_ParseIV(tline, header.m_iv); continue; } if(0 == CompareNoCase(field, SBB_CONTENT_DOMAIN)) { // Silently ignore // Content-Domain: RFC822 continue; } if(!field.empty()) { const char* ptr = (char*)field.begin(); size_t len = field.size(); string m(ptr, len); throw NotImplemented("PEM_StripEncapsulatedHeader: " + m + " not supported"); } } if(header.m_algorithm.empty()) throw InvalidArgument("PEM_StripEncapsulatedHeader: no encryption algorithm"); if(header.m_iv.empty()) throw InvalidArgument("PEM_StripEncapsulatedHeader: no IV present"); // After the empty line is the encapsulated text. Transfer it to the destination. src.TransferTo(dest); }
size_t PEM_ReadLine(BufferedTransformation& source, SecByteBlock& line, SecByteBlock& ending) { if(!source.AnyRetrievable()) { line.New(0); ending.New(0); return 0; } ByteQueue temp; while(source.AnyRetrievable()) { byte b; if(!source.Get(b)) throw Exception(Exception::OTHER_ERROR, "PEM_ReadLine: failed to read byte"); // LF ? if(b == '\n') { ending = LF; break; } // CR ? if(b == '\r') { // CRLF ? if(source.AnyRetrievable() && source.Peek(b)) { if(b == '\n') { source.Skip(1); ending = CRLF; break; } } ending = CR; break; } // Not End-of-Line, accumulate it. temp.Put(b); } if(temp.AnyRetrievable()) { line.Grow(temp.MaxRetrievable()); temp.Get(line.data(), line.size()); } else { line.New(0); ending.New(0); } // We return a line stripped of CRs and LFs. However, we return the actual number of // of bytes processed, including the CR and LF. A return of 0 means nothing was read. // A return of 1 means an empty line was read (CR or LF). A return of 2 could // mean an empty line was read (CRLF), or could mean 1 character was read. In // any case, line will hold whatever was parsed. return line.size() + ending.size(); }