...

Source file src/github.com/ProtonMail/go-crypto/openpgp/armor/armor.go

Documentation: github.com/ProtonMail/go-crypto/openpgp/armor

     1  // Copyright 2010 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is
     6  // very similar to PEM except that it has an additional CRC checksum.
     7  package armor // import "github.com/ProtonMail/go-crypto/openpgp/armor"
     8  
     9  import (
    10  	"bufio"
    11  	"bytes"
    12  	"encoding/base64"
    13  	"io"
    14  
    15  	"github.com/ProtonMail/go-crypto/openpgp/errors"
    16  )
    17  
    18  // A Block represents an OpenPGP armored structure.
    19  //
    20  // The encoded form is:
    21  //
    22  //	-----BEGIN Type-----
    23  //	Headers
    24  //
    25  //	base64-encoded Bytes
    26  //	'=' base64 encoded checksum
    27  //	-----END Type-----
    28  //
    29  // where Headers is a possibly empty sequence of Key: Value lines.
    30  //
    31  // Since the armored data can be very large, this package presents a streaming
    32  // interface.
    33  type Block struct {
    34  	Type    string            // The type, taken from the preamble (i.e. "PGP SIGNATURE").
    35  	Header  map[string]string // Optional headers.
    36  	Body    io.Reader         // A Reader from which the contents can be read
    37  	lReader lineReader
    38  	oReader openpgpReader
    39  }
    40  
    41  var ArmorCorrupt error = errors.StructuralError("armor invalid")
    42  
    43  const crc24Init = 0xb704ce
    44  const crc24Poly = 0x1864cfb
    45  const crc24Mask = 0xffffff
    46  
    47  // crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1
    48  func crc24(crc uint32, d []byte) uint32 {
    49  	for _, b := range d {
    50  		crc ^= uint32(b) << 16
    51  		for i := 0; i < 8; i++ {
    52  			crc <<= 1
    53  			if crc&0x1000000 != 0 {
    54  				crc ^= crc24Poly
    55  			}
    56  		}
    57  	}
    58  	return crc
    59  }
    60  
    61  var armorStart = []byte("-----BEGIN ")
    62  var armorEnd = []byte("-----END ")
    63  var armorEndOfLine = []byte("-----")
    64  
    65  // lineReader wraps a line based reader. It watches for the end of an armor
    66  // block and records the expected CRC value.
    67  type lineReader struct {
    68  	in     *bufio.Reader
    69  	buf    []byte
    70  	eof    bool
    71  	crc    uint32
    72  	crcSet bool
    73  }
    74  
    75  func (l *lineReader) Read(p []byte) (n int, err error) {
    76  	if l.eof {
    77  		return 0, io.EOF
    78  	}
    79  
    80  	if len(l.buf) > 0 {
    81  		n = copy(p, l.buf)
    82  		l.buf = l.buf[n:]
    83  		return
    84  	}
    85  
    86  	line, isPrefix, err := l.in.ReadLine()
    87  	if err != nil {
    88  		return
    89  	}
    90  	if isPrefix {
    91  		return 0, ArmorCorrupt
    92  	}
    93  
    94  	if bytes.HasPrefix(line, armorEnd) {
    95  		l.eof = true
    96  		return 0, io.EOF
    97  	}
    98  
    99  	if len(line) == 5 && line[0] == '=' {
   100  		// This is the checksum line
   101  		var expectedBytes [3]byte
   102  		var m int
   103  		m, err = base64.StdEncoding.Decode(expectedBytes[0:], line[1:])
   104  		if m != 3 || err != nil {
   105  			return
   106  		}
   107  		l.crc = uint32(expectedBytes[0])<<16 |
   108  			uint32(expectedBytes[1])<<8 |
   109  			uint32(expectedBytes[2])
   110  
   111  		line, _, err = l.in.ReadLine()
   112  		if err != nil && err != io.EOF {
   113  			return
   114  		}
   115  		if !bytes.HasPrefix(line, armorEnd) {
   116  			return 0, ArmorCorrupt
   117  		}
   118  
   119  		l.eof = true
   120  		l.crcSet = true
   121  		return 0, io.EOF
   122  	}
   123  
   124  	if len(line) > 96 {
   125  		return 0, ArmorCorrupt
   126  	}
   127  
   128  	n = copy(p, line)
   129  	bytesToSave := len(line) - n
   130  	if bytesToSave > 0 {
   131  		if cap(l.buf) < bytesToSave {
   132  			l.buf = make([]byte, 0, bytesToSave)
   133  		}
   134  		l.buf = l.buf[0:bytesToSave]
   135  		copy(l.buf, line[n:])
   136  	}
   137  
   138  	return
   139  }
   140  
   141  // openpgpReader passes Read calls to the underlying base64 decoder, but keeps
   142  // a running CRC of the resulting data and checks the CRC against the value
   143  // found by the lineReader at EOF.
   144  type openpgpReader struct {
   145  	lReader    *lineReader
   146  	b64Reader  io.Reader
   147  	currentCRC uint32
   148  }
   149  
   150  func (r *openpgpReader) Read(p []byte) (n int, err error) {
   151  	n, err = r.b64Reader.Read(p)
   152  	r.currentCRC = crc24(r.currentCRC, p[:n])
   153  
   154  	if err == io.EOF && r.lReader.crcSet && r.lReader.crc != uint32(r.currentCRC&crc24Mask) {
   155  		return 0, ArmorCorrupt
   156  	}
   157  
   158  	return
   159  }
   160  
   161  // Decode reads a PGP armored block from the given Reader. It will ignore
   162  // leading garbage. If it doesn't find a block, it will return nil, io.EOF. The
   163  // given Reader is not usable after calling this function: an arbitrary amount
   164  // of data may have been read past the end of the block.
   165  func Decode(in io.Reader) (p *Block, err error) {
   166  	r := bufio.NewReaderSize(in, 100)
   167  	var line []byte
   168  	ignoreNext := false
   169  
   170  TryNextBlock:
   171  	p = nil
   172  
   173  	// Skip leading garbage
   174  	for {
   175  		ignoreThis := ignoreNext
   176  		line, ignoreNext, err = r.ReadLine()
   177  		if err != nil {
   178  			return
   179  		}
   180  		if ignoreNext || ignoreThis {
   181  			continue
   182  		}
   183  		line = bytes.TrimSpace(line)
   184  		if len(line) > len(armorStart)+len(armorEndOfLine) && bytes.HasPrefix(line, armorStart) {
   185  			break
   186  		}
   187  	}
   188  
   189  	p = new(Block)
   190  	p.Type = string(line[len(armorStart) : len(line)-len(armorEndOfLine)])
   191  	p.Header = make(map[string]string)
   192  	nextIsContinuation := false
   193  	var lastKey string
   194  
   195  	// Read headers
   196  	for {
   197  		isContinuation := nextIsContinuation
   198  		line, nextIsContinuation, err = r.ReadLine()
   199  		if err != nil {
   200  			p = nil
   201  			return
   202  		}
   203  		if isContinuation {
   204  			p.Header[lastKey] += string(line)
   205  			continue
   206  		}
   207  		line = bytes.TrimSpace(line)
   208  		if len(line) == 0 {
   209  			break
   210  		}
   211  
   212  		i := bytes.Index(line, []byte(":"))
   213  		if i == -1 {
   214  			goto TryNextBlock
   215  		}
   216  		lastKey = string(line[:i])
   217  		var value string
   218  		if len(line) > i+2 {
   219  			value = string(line[i+2:])
   220  		}
   221  		p.Header[lastKey] = value
   222  	}
   223  
   224  	p.lReader.in = r
   225  	p.oReader.currentCRC = crc24Init
   226  	p.oReader.lReader = &p.lReader
   227  	p.oReader.b64Reader = base64.NewDecoder(base64.StdEncoding, &p.lReader)
   228  	p.Body = &p.oReader
   229  
   230  	return
   231  }
   232  

View as plain text