Bump github.com/go-git/go-git/v5 from 5.4.2 to 5.11.0
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.4.2 to 5.11.0. - [Release notes](https://github.com/go-git/go-git/releases) - [Commits](https://github.com/go-git/go-git/compare/v5.4.2...v5.11.0) --- updated-dependencies: - dependency-name: github.com/go-git/go-git/v5 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <[email protected]>
This commit is contained in:
+16
-5
@@ -4,6 +4,14 @@ package packet
|
||||
|
||||
import "math/bits"
|
||||
|
||||
// CipherSuite contains a combination of Cipher and Mode
|
||||
type CipherSuite struct {
|
||||
// The cipher function
|
||||
Cipher CipherFunction
|
||||
// The AEAD mode of operation.
|
||||
Mode AEADMode
|
||||
}
|
||||
|
||||
// AEADConfig collects a number of AEAD parameters along with sensible defaults.
|
||||
// A nil AEADConfig is valid and results in all default values.
|
||||
type AEADConfig struct {
|
||||
@@ -15,12 +23,13 @@ type AEADConfig struct {
|
||||
|
||||
// Mode returns the AEAD mode of operation.
|
||||
func (conf *AEADConfig) Mode() AEADMode {
|
||||
// If no preference is specified, OCB is used (which is mandatory to implement).
|
||||
if conf == nil || conf.DefaultMode == 0 {
|
||||
return AEADModeEAX
|
||||
return AEADModeOCB
|
||||
}
|
||||
|
||||
mode := conf.DefaultMode
|
||||
if mode != AEADModeEAX && mode != AEADModeOCB &&
|
||||
mode != AEADModeExperimentalGCM {
|
||||
if mode != AEADModeEAX && mode != AEADModeOCB && mode != AEADModeGCM {
|
||||
panic("AEAD mode unsupported")
|
||||
}
|
||||
return mode
|
||||
@@ -28,6 +37,8 @@ func (conf *AEADConfig) Mode() AEADMode {
|
||||
|
||||
// ChunkSizeByte returns the byte indicating the chunk size. The effective
|
||||
// chunk size is computed with the formula uint64(1) << (chunkSizeByte + 6)
|
||||
// limit to 16 = 4 MiB
|
||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
|
||||
func (conf *AEADConfig) ChunkSizeByte() byte {
|
||||
if conf == nil || conf.ChunkSize == 0 {
|
||||
return 12 // 1 << (12 + 6) == 262144 bytes
|
||||
@@ -38,8 +49,8 @@ func (conf *AEADConfig) ChunkSizeByte() byte {
|
||||
switch {
|
||||
case exponent < 6:
|
||||
exponent = 6
|
||||
case exponent > 27:
|
||||
exponent = 27
|
||||
case exponent > 16:
|
||||
exponent = 16
|
||||
}
|
||||
|
||||
return byte(exponent - 6)
|
||||
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
// Copyright (C) 2019 ProtonTech AG
|
||||
|
||||
package packet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
)
|
||||
|
||||
// aeadCrypter is an AEAD opener/sealer, its configuration, and data for en/decryption.
|
||||
type aeadCrypter struct {
|
||||
aead cipher.AEAD
|
||||
chunkSize int
|
||||
initialNonce []byte
|
||||
associatedData []byte // Chunk-independent associated data
|
||||
chunkIndex []byte // Chunk counter
|
||||
packetTag packetType // SEIP packet (v2) or AEAD Encrypted Data packet
|
||||
bytesProcessed int // Amount of plaintext bytes encrypted/decrypted
|
||||
buffer bytes.Buffer // Buffered bytes across chunks
|
||||
}
|
||||
|
||||
// computeNonce takes the incremental index and computes an eXclusive OR with
|
||||
// the least significant 8 bytes of the receivers' initial nonce (see sec.
|
||||
// 5.16.1 and 5.16.2). It returns the resulting nonce.
|
||||
func (wo *aeadCrypter) computeNextNonce() (nonce []byte) {
|
||||
if wo.packetTag == packetTypeSymmetricallyEncryptedIntegrityProtected {
|
||||
return append(wo.initialNonce, wo.chunkIndex...)
|
||||
}
|
||||
|
||||
nonce = make([]byte, len(wo.initialNonce))
|
||||
copy(nonce, wo.initialNonce)
|
||||
offset := len(wo.initialNonce) - 8
|
||||
for i := 0; i < 8; i++ {
|
||||
nonce[i+offset] ^= wo.chunkIndex[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// incrementIndex performs an integer increment by 1 of the integer represented by the
|
||||
// slice, modifying it accordingly.
|
||||
func (wo *aeadCrypter) incrementIndex() error {
|
||||
index := wo.chunkIndex
|
||||
if len(index) == 0 {
|
||||
return errors.AEADError("Index has length 0")
|
||||
}
|
||||
for i := len(index) - 1; i >= 0; i-- {
|
||||
if index[i] < 255 {
|
||||
index[i]++
|
||||
return nil
|
||||
}
|
||||
index[i] = 0
|
||||
}
|
||||
return errors.AEADError("cannot further increment index")
|
||||
}
|
||||
|
||||
// aeadDecrypter reads and decrypts bytes. It buffers extra decrypted bytes when
|
||||
// necessary, similar to aeadEncrypter.
|
||||
type aeadDecrypter struct {
|
||||
aeadCrypter // Embedded ciphertext opener
|
||||
reader io.Reader // 'reader' is a partialLengthReader
|
||||
peekedBytes []byte // Used to detect last chunk
|
||||
eof bool
|
||||
}
|
||||
|
||||
// Read decrypts bytes and reads them into dst. It decrypts when necessary and
|
||||
// buffers extra decrypted bytes. It returns the number of bytes copied into dst
|
||||
// and an error.
|
||||
func (ar *aeadDecrypter) Read(dst []byte) (n int, err error) {
|
||||
// Return buffered plaintext bytes from previous calls
|
||||
if ar.buffer.Len() > 0 {
|
||||
return ar.buffer.Read(dst)
|
||||
}
|
||||
|
||||
// Return EOF if we've previously validated the final tag
|
||||
if ar.eof {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
// Read a chunk
|
||||
tagLen := ar.aead.Overhead()
|
||||
cipherChunkBuf := new(bytes.Buffer)
|
||||
_, errRead := io.CopyN(cipherChunkBuf, ar.reader, int64(ar.chunkSize+tagLen))
|
||||
cipherChunk := cipherChunkBuf.Bytes()
|
||||
if errRead != nil && errRead != io.EOF {
|
||||
return 0, errRead
|
||||
}
|
||||
decrypted, errChunk := ar.openChunk(cipherChunk)
|
||||
if errChunk != nil {
|
||||
return 0, errChunk
|
||||
}
|
||||
|
||||
// Return decrypted bytes, buffering if necessary
|
||||
if len(dst) < len(decrypted) {
|
||||
n = copy(dst, decrypted[:len(dst)])
|
||||
ar.buffer.Write(decrypted[len(dst):])
|
||||
} else {
|
||||
n = copy(dst, decrypted)
|
||||
}
|
||||
|
||||
// Check final authentication tag
|
||||
if errRead == io.EOF {
|
||||
errChunk := ar.validateFinalTag(ar.peekedBytes)
|
||||
if errChunk != nil {
|
||||
return n, errChunk
|
||||
}
|
||||
ar.eof = true // Mark EOF for when we've returned all buffered data
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close is noOp. The final authentication tag of the stream was already
|
||||
// checked in the last Read call. In the future, this function could be used to
|
||||
// wipe the reader and peeked, decrypted bytes, if necessary.
|
||||
func (ar *aeadDecrypter) Close() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// openChunk decrypts and checks integrity of an encrypted chunk, returning
|
||||
// the underlying plaintext and an error. It accesses peeked bytes from next
|
||||
// chunk, to identify the last chunk and decrypt/validate accordingly.
|
||||
func (ar *aeadDecrypter) openChunk(data []byte) ([]byte, error) {
|
||||
tagLen := ar.aead.Overhead()
|
||||
// Restore carried bytes from last call
|
||||
chunkExtra := append(ar.peekedBytes, data...)
|
||||
// 'chunk' contains encrypted bytes, followed by an authentication tag.
|
||||
chunk := chunkExtra[:len(chunkExtra)-tagLen]
|
||||
ar.peekedBytes = chunkExtra[len(chunkExtra)-tagLen:]
|
||||
|
||||
adata := ar.associatedData
|
||||
if ar.aeadCrypter.packetTag == packetTypeAEADEncrypted {
|
||||
adata = append(ar.associatedData, ar.chunkIndex...)
|
||||
}
|
||||
|
||||
nonce := ar.computeNextNonce()
|
||||
plainChunk, err := ar.aead.Open(nil, nonce, chunk, adata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ar.bytesProcessed += len(plainChunk)
|
||||
if err = ar.aeadCrypter.incrementIndex(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plainChunk, nil
|
||||
}
|
||||
|
||||
// Checks the summary tag. It takes into account the total decrypted bytes into
|
||||
// the associated data. It returns an error, or nil if the tag is valid.
|
||||
func (ar *aeadDecrypter) validateFinalTag(tag []byte) error {
|
||||
// Associated: tag, version, cipher, aead, chunk size, ...
|
||||
amountBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(amountBytes, uint64(ar.bytesProcessed))
|
||||
|
||||
adata := ar.associatedData
|
||||
if ar.aeadCrypter.packetTag == packetTypeAEADEncrypted {
|
||||
// ... index ...
|
||||
adata = append(ar.associatedData, ar.chunkIndex...)
|
||||
}
|
||||
|
||||
// ... and total number of encrypted octets
|
||||
adata = append(adata, amountBytes...)
|
||||
nonce := ar.computeNextNonce()
|
||||
_, err := ar.aead.Open(nil, nonce, tag, adata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// aeadEncrypter encrypts and writes bytes. It encrypts when necessary according
|
||||
// to the AEAD block size, and buffers the extra encrypted bytes for next write.
|
||||
type aeadEncrypter struct {
|
||||
aeadCrypter // Embedded plaintext sealer
|
||||
writer io.WriteCloser // 'writer' is a partialLengthWriter
|
||||
}
|
||||
|
||||
// Write encrypts and writes bytes. It encrypts when necessary and buffers extra
|
||||
// plaintext bytes for next call. When the stream is finished, Close() MUST be
|
||||
// called to append the final tag.
|
||||
func (aw *aeadEncrypter) Write(plaintextBytes []byte) (n int, err error) {
|
||||
// Append plaintextBytes to existing buffered bytes
|
||||
n, err = aw.buffer.Write(plaintextBytes)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
// Encrypt and write chunks
|
||||
for aw.buffer.Len() >= aw.chunkSize {
|
||||
plainChunk := aw.buffer.Next(aw.chunkSize)
|
||||
encryptedChunk, err := aw.sealChunk(plainChunk)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
_, err = aw.writer.Write(encryptedChunk)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close encrypts and writes the remaining buffered plaintext if any, appends
|
||||
// the final authentication tag, and closes the embedded writer. This function
|
||||
// MUST be called at the end of a stream.
|
||||
func (aw *aeadEncrypter) Close() (err error) {
|
||||
// Encrypt and write a chunk if there's buffered data left, or if we haven't
|
||||
// written any chunks yet.
|
||||
if aw.buffer.Len() > 0 || aw.bytesProcessed == 0 {
|
||||
plainChunk := aw.buffer.Bytes()
|
||||
lastEncryptedChunk, err := aw.sealChunk(plainChunk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = aw.writer.Write(lastEncryptedChunk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Compute final tag (associated data: packet tag, version, cipher, aead,
|
||||
// chunk size...
|
||||
adata := aw.associatedData
|
||||
|
||||
if aw.aeadCrypter.packetTag == packetTypeAEADEncrypted {
|
||||
// ... index ...
|
||||
adata = append(aw.associatedData, aw.chunkIndex...)
|
||||
}
|
||||
|
||||
// ... and total number of encrypted octets
|
||||
amountBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(amountBytes, uint64(aw.bytesProcessed))
|
||||
adata = append(adata, amountBytes...)
|
||||
|
||||
nonce := aw.computeNextNonce()
|
||||
finalTag := aw.aead.Seal(nil, nonce, nil, adata)
|
||||
_, err = aw.writer.Write(finalTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return aw.writer.Close()
|
||||
}
|
||||
|
||||
// sealChunk Encrypts and authenticates the given chunk.
|
||||
func (aw *aeadEncrypter) sealChunk(data []byte) ([]byte, error) {
|
||||
if len(data) > aw.chunkSize {
|
||||
return nil, errors.AEADError("chunk exceeds maximum length")
|
||||
}
|
||||
if aw.associatedData == nil {
|
||||
return nil, errors.AEADError("can't seal without headers")
|
||||
}
|
||||
adata := aw.associatedData
|
||||
if aw.aeadCrypter.packetTag == packetTypeAEADEncrypted {
|
||||
adata = append(aw.associatedData, aw.chunkIndex...)
|
||||
}
|
||||
|
||||
nonce := aw.computeNextNonce()
|
||||
encrypted := aw.aead.Seal(nil, nonce, data, adata)
|
||||
aw.bytesProcessed += len(data)
|
||||
if err := aw.aeadCrypter.incrementIndex(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return encrypted, nil
|
||||
}
|
||||
+11
-279
@@ -3,17 +3,14 @@
|
||||
package packet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
||||
)
|
||||
|
||||
// AEADEncrypted represents an AEAD Encrypted Packet (tag 20, RFC4880bis-5.16).
|
||||
// AEADEncrypted represents an AEAD Encrypted Packet.
|
||||
// See https://www.ietf.org/archive/id/draft-koch-openpgp-2015-rfc4880bis-00.html#name-aead-encrypted-data-packet-t
|
||||
type AEADEncrypted struct {
|
||||
cipher CipherFunction
|
||||
mode AEADMode
|
||||
@@ -25,33 +22,6 @@ type AEADEncrypted struct {
|
||||
// Only currently defined version
|
||||
const aeadEncryptedVersion = 1
|
||||
|
||||
// An AEAD opener/sealer, its configuration, and data for en/decryption.
|
||||
type aeadCrypter struct {
|
||||
aead cipher.AEAD
|
||||
chunkSize int
|
||||
initialNonce []byte
|
||||
associatedData []byte // Chunk-independent associated data
|
||||
chunkIndex []byte // Chunk counter
|
||||
bytesProcessed int // Amount of plaintext bytes encrypted/decrypted
|
||||
buffer bytes.Buffer // Buffered bytes accross chunks
|
||||
}
|
||||
|
||||
// aeadEncrypter encrypts and writes bytes. It encrypts when necessary according
|
||||
// to the AEAD block size, and buffers the extra encrypted bytes for next write.
|
||||
type aeadEncrypter struct {
|
||||
aeadCrypter // Embedded plaintext sealer
|
||||
writer io.WriteCloser // 'writer' is a partialLengthWriter
|
||||
}
|
||||
|
||||
// aeadDecrypter reads and decrypts bytes. It buffers extra decrypted bytes when
|
||||
// necessary, similar to aeadEncrypter.
|
||||
type aeadDecrypter struct {
|
||||
aeadCrypter // Embedded ciphertext opener
|
||||
reader io.Reader // 'reader' is a partialLengthReader
|
||||
peekedBytes []byte // Used to detect last chunk
|
||||
eof bool
|
||||
}
|
||||
|
||||
func (ae *AEADEncrypted) parse(buf io.Reader) error {
|
||||
headerData := make([]byte, 4)
|
||||
if n, err := io.ReadFull(buf, headerData); n < 4 {
|
||||
@@ -59,10 +29,14 @@ func (ae *AEADEncrypted) parse(buf io.Reader) error {
|
||||
}
|
||||
// Read initial nonce
|
||||
mode := AEADMode(headerData[2])
|
||||
nonceLen := mode.NonceLength()
|
||||
if nonceLen == 0 {
|
||||
nonceLen := mode.IvLength()
|
||||
|
||||
// This packet supports only EAX and OCB
|
||||
// https://www.ietf.org/archive/id/draft-koch-openpgp-2015-rfc4880bis-00.html#name-aead-encrypted-data-packet-t
|
||||
if nonceLen == 0 || mode > AEADModeOCB {
|
||||
return errors.AEADError("unknown mode")
|
||||
}
|
||||
|
||||
initialNonce := make([]byte, nonceLen)
|
||||
if n, err := io.ReadFull(buf, initialNonce); n < nonceLen {
|
||||
return errors.AEADError("could not read aead nonce:" + err.Error())
|
||||
@@ -75,7 +49,7 @@ func (ae *AEADEncrypted) parse(buf io.Reader) error {
|
||||
}
|
||||
ae.cipher = CipherFunction(c)
|
||||
ae.mode = mode
|
||||
ae.chunkSizeByte = byte(headerData[3])
|
||||
ae.chunkSizeByte = headerData[3]
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -105,225 +79,13 @@ func (ae *AEADEncrypted) decrypt(key []byte) (io.ReadCloser, error) {
|
||||
initialNonce: ae.initialNonce,
|
||||
associatedData: ae.associatedData(),
|
||||
chunkIndex: make([]byte, 8),
|
||||
packetTag: packetTypeAEADEncrypted,
|
||||
},
|
||||
reader: ae.Contents,
|
||||
peekedBytes: peekedBytes}, nil
|
||||
}
|
||||
|
||||
// Read decrypts bytes and reads them into dst. It decrypts when necessary and
|
||||
// buffers extra decrypted bytes. It returns the number of bytes copied into dst
|
||||
// and an error.
|
||||
func (ar *aeadDecrypter) Read(dst []byte) (n int, err error) {
|
||||
// Return buffered plaintext bytes from previous calls
|
||||
if ar.buffer.Len() > 0 {
|
||||
return ar.buffer.Read(dst)
|
||||
}
|
||||
|
||||
// Return EOF if we've previously validated the final tag
|
||||
if ar.eof {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
// Read a chunk
|
||||
tagLen := ar.aead.Overhead()
|
||||
cipherChunkBuf := new(bytes.Buffer)
|
||||
_, errRead := io.CopyN(cipherChunkBuf, ar.reader, int64(ar.chunkSize + tagLen))
|
||||
cipherChunk := cipherChunkBuf.Bytes()
|
||||
if errRead != nil && errRead != io.EOF {
|
||||
return 0, errRead
|
||||
}
|
||||
decrypted, errChunk := ar.openChunk(cipherChunk)
|
||||
if errChunk != nil {
|
||||
return 0, errChunk
|
||||
}
|
||||
|
||||
// Return decrypted bytes, buffering if necessary
|
||||
if len(dst) < len(decrypted) {
|
||||
n = copy(dst, decrypted[:len(dst)])
|
||||
ar.buffer.Write(decrypted[len(dst):])
|
||||
} else {
|
||||
n = copy(dst, decrypted)
|
||||
}
|
||||
|
||||
// Check final authentication tag
|
||||
if errRead == io.EOF {
|
||||
errChunk := ar.validateFinalTag(ar.peekedBytes)
|
||||
if errChunk != nil {
|
||||
return n, errChunk
|
||||
}
|
||||
ar.eof = true // Mark EOF for when we've returned all buffered data
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close is noOp. The final authentication tag of the stream was already
|
||||
// checked in the last Read call. In the future, this function could be used to
|
||||
// wipe the reader and peeked, decrypted bytes, if necessary.
|
||||
func (ar *aeadDecrypter) Close() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SerializeAEADEncrypted initializes the aeadCrypter and returns a writer.
|
||||
// This writer encrypts and writes bytes (see aeadEncrypter.Write()).
|
||||
func SerializeAEADEncrypted(w io.Writer, key []byte, cipher CipherFunction, mode AEADMode, config *Config) (io.WriteCloser, error) {
|
||||
writeCloser := noOpCloser{w}
|
||||
writer, err := serializeStreamHeader(writeCloser, packetTypeAEADEncrypted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Data for en/decryption: tag, version, cipher, aead mode, chunk size
|
||||
aeadConf := config.AEAD()
|
||||
prefix := []byte{
|
||||
0xD4,
|
||||
aeadEncryptedVersion,
|
||||
byte(config.Cipher()),
|
||||
byte(aeadConf.Mode()),
|
||||
aeadConf.ChunkSizeByte(),
|
||||
}
|
||||
n, err := writer.Write(prefix[1:])
|
||||
if err != nil || n < 4 {
|
||||
return nil, errors.AEADError("could not write AEAD headers")
|
||||
}
|
||||
// Sample nonce
|
||||
nonceLen := aeadConf.Mode().NonceLength()
|
||||
nonce := make([]byte, nonceLen)
|
||||
n, err = rand.Read(nonce)
|
||||
if err != nil {
|
||||
panic("Could not sample random nonce")
|
||||
}
|
||||
_, err = writer.Write(nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockCipher := CipherFunction(config.Cipher()).new(key)
|
||||
alg := AEADMode(aeadConf.Mode()).new(blockCipher)
|
||||
|
||||
chunkSize := decodeAEADChunkSize(aeadConf.ChunkSizeByte())
|
||||
return &aeadEncrypter{
|
||||
aeadCrypter: aeadCrypter{
|
||||
aead: alg,
|
||||
chunkSize: chunkSize,
|
||||
associatedData: prefix,
|
||||
chunkIndex: make([]byte, 8),
|
||||
initialNonce: nonce,
|
||||
},
|
||||
writer: writer}, nil
|
||||
}
|
||||
|
||||
// Write encrypts and writes bytes. It encrypts when necessary and buffers extra
|
||||
// plaintext bytes for next call. When the stream is finished, Close() MUST be
|
||||
// called to append the final tag.
|
||||
func (aw *aeadEncrypter) Write(plaintextBytes []byte) (n int, err error) {
|
||||
// Append plaintextBytes to existing buffered bytes
|
||||
n, err = aw.buffer.Write(plaintextBytes)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
// Encrypt and write chunks
|
||||
for aw.buffer.Len() >= aw.chunkSize {
|
||||
plainChunk := aw.buffer.Next(aw.chunkSize)
|
||||
encryptedChunk, err := aw.sealChunk(plainChunk)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
_, err = aw.writer.Write(encryptedChunk)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close encrypts and writes the remaining buffered plaintext if any, appends
|
||||
// the final authentication tag, and closes the embedded writer. This function
|
||||
// MUST be called at the end of a stream.
|
||||
func (aw *aeadEncrypter) Close() (err error) {
|
||||
// Encrypt and write a chunk if there's buffered data left, or if we haven't
|
||||
// written any chunks yet.
|
||||
if aw.buffer.Len() > 0 || aw.bytesProcessed == 0 {
|
||||
plainChunk := aw.buffer.Bytes()
|
||||
lastEncryptedChunk, err := aw.sealChunk(plainChunk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = aw.writer.Write(lastEncryptedChunk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Compute final tag (associated data: packet tag, version, cipher, aead,
|
||||
// chunk size, index, total number of encrypted octets).
|
||||
adata := append(aw.associatedData[:], aw.chunkIndex[:]...)
|
||||
adata = append(adata, make([]byte, 8)...)
|
||||
binary.BigEndian.PutUint64(adata[13:], uint64(aw.bytesProcessed))
|
||||
nonce := aw.computeNextNonce()
|
||||
finalTag := aw.aead.Seal(nil, nonce, nil, adata)
|
||||
_, err = aw.writer.Write(finalTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return aw.writer.Close()
|
||||
}
|
||||
|
||||
// sealChunk Encrypts and authenticates the given chunk.
|
||||
func (aw *aeadEncrypter) sealChunk(data []byte) ([]byte, error) {
|
||||
if len(data) > aw.chunkSize {
|
||||
return nil, errors.AEADError("chunk exceeds maximum length")
|
||||
}
|
||||
if aw.associatedData == nil {
|
||||
return nil, errors.AEADError("can't seal without headers")
|
||||
}
|
||||
adata := append(aw.associatedData, aw.chunkIndex...)
|
||||
nonce := aw.computeNextNonce()
|
||||
encrypted := aw.aead.Seal(nil, nonce, data, adata)
|
||||
aw.bytesProcessed += len(data)
|
||||
if err := aw.aeadCrypter.incrementIndex(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return encrypted, nil
|
||||
}
|
||||
|
||||
// openChunk decrypts and checks integrity of an encrypted chunk, returning
|
||||
// the underlying plaintext and an error. It access peeked bytes from next
|
||||
// chunk, to identify the last chunk and decrypt/validate accordingly.
|
||||
func (ar *aeadDecrypter) openChunk(data []byte) ([]byte, error) {
|
||||
tagLen := ar.aead.Overhead()
|
||||
// Restore carried bytes from last call
|
||||
chunkExtra := append(ar.peekedBytes, data...)
|
||||
// 'chunk' contains encrypted bytes, followed by an authentication tag.
|
||||
chunk := chunkExtra[:len(chunkExtra)-tagLen]
|
||||
ar.peekedBytes = chunkExtra[len(chunkExtra)-tagLen:]
|
||||
adata := append(ar.associatedData, ar.chunkIndex...)
|
||||
nonce := ar.computeNextNonce()
|
||||
plainChunk, err := ar.aead.Open(nil, nonce, chunk, adata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ar.bytesProcessed += len(plainChunk)
|
||||
if err = ar.aeadCrypter.incrementIndex(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plainChunk, nil
|
||||
}
|
||||
|
||||
// Checks the summary tag. It takes into account the total decrypted bytes into
|
||||
// the associated data. It returns an error, or nil if the tag is valid.
|
||||
func (ar *aeadDecrypter) validateFinalTag(tag []byte) error {
|
||||
// Associated: tag, version, cipher, aead, chunk size, index, and octets
|
||||
amountBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(amountBytes, uint64(ar.bytesProcessed))
|
||||
adata := append(ar.associatedData, ar.chunkIndex...)
|
||||
adata = append(adata, amountBytes...)
|
||||
nonce := ar.computeNextNonce()
|
||||
_, err := ar.aead.Open(nil, nonce, tag, adata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Associated data for chunks: tag, version, cipher, mode, chunk size byte
|
||||
// associatedData for chunks: tag, version, cipher, mode, chunk size byte
|
||||
func (ae *AEADEncrypted) associatedData() []byte {
|
||||
return []byte{
|
||||
0xD4,
|
||||
@@ -332,33 +94,3 @@ func (ae *AEADEncrypted) associatedData() []byte {
|
||||
byte(ae.mode),
|
||||
ae.chunkSizeByte}
|
||||
}
|
||||
|
||||
// computeNonce takes the incremental index and computes an eXclusive OR with
|
||||
// the least significant 8 bytes of the receivers' initial nonce (see sec.
|
||||
// 5.16.1 and 5.16.2). It returns the resulting nonce.
|
||||
func (wo *aeadCrypter) computeNextNonce() (nonce []byte) {
|
||||
nonce = make([]byte, len(wo.initialNonce))
|
||||
copy(nonce, wo.initialNonce)
|
||||
offset := len(wo.initialNonce) - 8
|
||||
for i := 0; i < 8; i++ {
|
||||
nonce[i+offset] ^= wo.chunkIndex[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// incrementIndex perfoms an integer increment by 1 of the integer represented by the
|
||||
// slice, modifying it accordingly.
|
||||
func (wo *aeadCrypter) incrementIndex() error {
|
||||
index := wo.chunkIndex
|
||||
if len(index) == 0 {
|
||||
return errors.AEADError("Index has length 0")
|
||||
}
|
||||
for i := len(index) - 1; i >= 0; i-- {
|
||||
if index[i] < 255 {
|
||||
index[i]++
|
||||
return nil
|
||||
}
|
||||
index[i] = 0
|
||||
}
|
||||
return errors.AEADError("cannot further increment index")
|
||||
}
|
||||
|
||||
+2
@@ -47,6 +47,8 @@ func (c *Compressed) parse(r io.Reader) error {
|
||||
}
|
||||
|
||||
switch buf[0] {
|
||||
case 0:
|
||||
c.Body = r
|
||||
case 1:
|
||||
c.Body = flate.NewReader(r)
|
||||
case 2:
|
||||
|
||||
+94
-13
@@ -10,6 +10,8 @@ import (
|
||||
"io"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/s2k"
|
||||
)
|
||||
|
||||
// Config collects a number of parameters along with sensible defaults.
|
||||
@@ -33,16 +35,24 @@ type Config struct {
|
||||
DefaultCompressionAlgo CompressionAlgo
|
||||
// CompressionConfig configures the compression settings.
|
||||
CompressionConfig *CompressionConfig
|
||||
// S2KCount is only used for symmetric encryption. It
|
||||
// determines the strength of the passphrase stretching when
|
||||
// S2K (String to Key) config, used for key derivation in the context of secret key encryption
|
||||
// and password-encrypted data.
|
||||
// If nil, the default configuration is used
|
||||
S2KConfig *s2k.Config
|
||||
// Iteration count for Iterated S2K (String to Key).
|
||||
// Only used if sk2.Mode is nil.
|
||||
// This value is duplicated here from s2k.Config for backwards compatibility.
|
||||
// It determines the strength of the passphrase stretching when
|
||||
// the said passphrase is hashed to produce a key. S2KCount
|
||||
// should be between 1024 and 65011712, inclusive. If Config
|
||||
// is nil or S2KCount is 0, the value 65536 used. Not all
|
||||
// should be between 65536 and 65011712, inclusive. If Config
|
||||
// is nil or S2KCount is 0, the value 16777216 used. Not all
|
||||
// values in the above range can be represented. S2KCount will
|
||||
// be rounded up to the next representable value if it cannot
|
||||
// be encoded exactly. When set, it is strongly encrouraged to
|
||||
// use a value that is at least 65536. See RFC 4880 Section
|
||||
// 3.7.1.3.
|
||||
//
|
||||
// Deprecated: SK2Count should be configured in S2KConfig instead.
|
||||
S2KCount int
|
||||
// RSABits is the number of bits in new RSA keys made with NewEntity.
|
||||
// If zero, then 2048 bit keys are created.
|
||||
@@ -52,6 +62,9 @@ type Config struct {
|
||||
Algorithm PublicKeyAlgorithm
|
||||
// Some known primes that are optionally prepopulated by the caller
|
||||
RSAPrimes []*big.Int
|
||||
// Curve configures the desired packet.Curve if the Algorithm is PubKeyAlgoECDSA,
|
||||
// PubKeyAlgoEdDSA, or PubKeyAlgoECDH. If empty Curve25519 is used.
|
||||
Curve Curve
|
||||
// AEADConfig configures the use of the new AEAD Encrypted Data Packet,
|
||||
// defined in the draft of the next version of the OpenPGP specification.
|
||||
// If a non-nil AEADConfig is passed, usage of this packet is enabled. By
|
||||
@@ -78,6 +91,25 @@ type Config struct {
|
||||
// By default, the signing key is selected automatically, preferring
|
||||
// signing subkeys if available.
|
||||
SigningKeyId uint64
|
||||
// SigningIdentity is used to specify a user ID (packet Signer's User ID, type 28)
|
||||
// when producing a generic certification signature onto an existing user ID.
|
||||
// The identity must be present in the signer Entity.
|
||||
SigningIdentity string
|
||||
// InsecureAllowUnauthenticatedMessages controls, whether it is tolerated to read
|
||||
// encrypted messages without Modification Detection Code (MDC).
|
||||
// MDC is mandated by the IETF OpenPGP Crypto Refresh draft and has long been implemented
|
||||
// in most OpenPGP implementations. Messages without MDC are considered unnecessarily
|
||||
// insecure and should be prevented whenever possible.
|
||||
// In case one needs to deal with messages from very old OpenPGP implementations, there
|
||||
// might be no other way than to tolerate the missing MDC. Setting this flag, allows this
|
||||
// mode of operation. It should be considered a measure of last resort.
|
||||
InsecureAllowUnauthenticatedMessages bool
|
||||
// KnownNotations is a map of Notation Data names to bools, which controls
|
||||
// the notation names that are allowed to be present in critical Notation Data
|
||||
// signature subpackets.
|
||||
KnownNotations map[string]bool
|
||||
// SignatureNotations is a list of Notations to be added to any signatures.
|
||||
SignatureNotations []*Notation
|
||||
}
|
||||
|
||||
func (c *Config) Random() io.Reader {
|
||||
@@ -103,9 +135,9 @@ func (c *Config) Cipher() CipherFunction {
|
||||
|
||||
func (c *Config) Now() time.Time {
|
||||
if c == nil || c.Time == nil {
|
||||
return time.Now()
|
||||
return time.Now().Truncate(time.Second)
|
||||
}
|
||||
return c.Time()
|
||||
return c.Time().Truncate(time.Second)
|
||||
}
|
||||
|
||||
// KeyLifetime returns the validity period of the key.
|
||||
@@ -131,13 +163,6 @@ func (c *Config) Compression() CompressionAlgo {
|
||||
return c.DefaultCompressionAlgo
|
||||
}
|
||||
|
||||
func (c *Config) PasswordHashIterations() int {
|
||||
if c == nil || c.S2KCount == 0 {
|
||||
return 0
|
||||
}
|
||||
return c.S2KCount
|
||||
}
|
||||
|
||||
func (c *Config) RSAModulusBits() int {
|
||||
if c == nil || c.RSABits == 0 {
|
||||
return 2048
|
||||
@@ -152,6 +177,34 @@ func (c *Config) PublicKeyAlgorithm() PublicKeyAlgorithm {
|
||||
return c.Algorithm
|
||||
}
|
||||
|
||||
func (c *Config) CurveName() Curve {
|
||||
if c == nil || c.Curve == "" {
|
||||
return Curve25519
|
||||
}
|
||||
return c.Curve
|
||||
}
|
||||
|
||||
// Deprecated: The hash iterations should now be queried via the S2K() method.
|
||||
func (c *Config) PasswordHashIterations() int {
|
||||
if c == nil || c.S2KCount == 0 {
|
||||
return 0
|
||||
}
|
||||
return c.S2KCount
|
||||
}
|
||||
|
||||
func (c *Config) S2K() *s2k.Config {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
// for backwards compatibility
|
||||
if c != nil && c.S2KCount > 0 && c.S2KConfig == nil {
|
||||
return &s2k.Config{
|
||||
S2KCount: c.S2KCount,
|
||||
}
|
||||
}
|
||||
return c.S2KConfig
|
||||
}
|
||||
|
||||
func (c *Config) AEAD() *AEADConfig {
|
||||
if c == nil {
|
||||
return nil
|
||||
@@ -165,3 +218,31 @@ func (c *Config) SigningKey() uint64 {
|
||||
}
|
||||
return c.SigningKeyId
|
||||
}
|
||||
|
||||
func (c *Config) SigningUserId() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return c.SigningIdentity
|
||||
}
|
||||
|
||||
func (c *Config) AllowUnauthenticatedMessages() bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
return c.InsecureAllowUnauthenticatedMessages
|
||||
}
|
||||
|
||||
func (c *Config) KnownNotation(notationName string) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
return c.KnownNotations[notationName]
|
||||
}
|
||||
|
||||
func (c *Config) Notations() []*Notation {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.SignatureNotations
|
||||
}
|
||||
|
||||
+5
-1
@@ -25,7 +25,7 @@ const encryptedKeyVersion = 3
|
||||
type EncryptedKey struct {
|
||||
KeyId uint64
|
||||
Algo PublicKeyAlgorithm
|
||||
CipherFunc CipherFunction // only valid after a successful Decrypt
|
||||
CipherFunc CipherFunction // only valid after a successful Decrypt for a v3 packet
|
||||
Key []byte // only valid after a successful Decrypt
|
||||
|
||||
encryptedMPI1, encryptedMPI2 encoding.Field
|
||||
@@ -123,6 +123,10 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error {
|
||||
}
|
||||
|
||||
e.CipherFunc = CipherFunction(b[0])
|
||||
if !e.CipherFunc.IsSupported() {
|
||||
return errors.UnsupportedError("unsupported encryption function")
|
||||
}
|
||||
|
||||
e.Key = b[1 : len(b)-2]
|
||||
expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1])
|
||||
checksum := checksumKeyMaterial(e.Key)
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package packet
|
||||
|
||||
// Notation type represents a Notation Data subpacket
|
||||
// see https://tools.ietf.org/html/rfc4880#section-5.2.3.16
|
||||
type Notation struct {
|
||||
Name string
|
||||
Value []byte
|
||||
IsCritical bool
|
||||
IsHumanReadable bool
|
||||
}
|
||||
|
||||
func (notation *Notation) getData() []byte {
|
||||
nameData := []byte(notation.Name)
|
||||
nameLen := len(nameData)
|
||||
valueLen := len(notation.Value)
|
||||
|
||||
data := make([]byte, 8+nameLen+valueLen)
|
||||
if notation.IsHumanReadable {
|
||||
data[0] = 0x80
|
||||
}
|
||||
|
||||
data[4] = byte(nameLen >> 8)
|
||||
data[5] = byte(nameLen)
|
||||
data[6] = byte(valueLen >> 8)
|
||||
data[7] = byte(valueLen)
|
||||
copy(data[8:8+nameLen], nameData)
|
||||
copy(data[8+nameLen:], notation.Value)
|
||||
return data
|
||||
}
|
||||
+3
-3
@@ -8,7 +8,7 @@ import (
|
||||
"crypto"
|
||||
"encoding/binary"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/s2k"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
@@ -37,7 +37,7 @@ func (ops *OnePassSignature) parse(r io.Reader) (err error) {
|
||||
}
|
||||
|
||||
var ok bool
|
||||
ops.Hash, ok = s2k.HashIdToHash(buf[2])
|
||||
ops.Hash, ok = algorithm.HashIdToHashWithSha1(buf[2])
|
||||
if !ok {
|
||||
return errors.UnsupportedError("hash function: " + strconv.Itoa(int(buf[2])))
|
||||
}
|
||||
@@ -55,7 +55,7 @@ func (ops *OnePassSignature) Serialize(w io.Writer) error {
|
||||
buf[0] = onePassSignatureVersion
|
||||
buf[1] = uint8(ops.SigType)
|
||||
var ok bool
|
||||
buf[2], ok = s2k.HashToHashId(ops.Hash)
|
||||
buf[2], ok = algorithm.HashToHashIdWithSha1(ops.Hash)
|
||||
if !ok {
|
||||
return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash)))
|
||||
}
|
||||
|
||||
+12
-3
@@ -84,8 +84,9 @@ func (or *OpaqueReader) Next() (op *OpaquePacket, err error) {
|
||||
// OpaqueSubpacket represents an unparsed OpenPGP subpacket,
|
||||
// as found in signature and user attribute packets.
|
||||
type OpaqueSubpacket struct {
|
||||
SubType uint8
|
||||
Contents []byte
|
||||
SubType uint8
|
||||
EncodedLength []byte // Store the original encoded length for signature verifications.
|
||||
Contents []byte
|
||||
}
|
||||
|
||||
// OpaqueSubpackets extracts opaque, unparsed OpenPGP subpackets from
|
||||
@@ -109,6 +110,7 @@ func OpaqueSubpackets(contents []byte) (result []*OpaqueSubpacket, err error) {
|
||||
func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacket, err error) {
|
||||
// RFC 4880, section 5.2.3.1
|
||||
var subLen uint32
|
||||
var encodedLength []byte
|
||||
if len(contents) < 1 {
|
||||
goto Truncated
|
||||
}
|
||||
@@ -119,6 +121,7 @@ func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacke
|
||||
if len(contents) < subHeaderLen {
|
||||
goto Truncated
|
||||
}
|
||||
encodedLength = contents[0:1]
|
||||
subLen = uint32(contents[0])
|
||||
contents = contents[1:]
|
||||
case contents[0] < 255:
|
||||
@@ -126,6 +129,7 @@ func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacke
|
||||
if len(contents) < subHeaderLen {
|
||||
goto Truncated
|
||||
}
|
||||
encodedLength = contents[0:2]
|
||||
subLen = uint32(contents[0]-192)<<8 + uint32(contents[1]) + 192
|
||||
contents = contents[2:]
|
||||
default:
|
||||
@@ -133,16 +137,19 @@ func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacke
|
||||
if len(contents) < subHeaderLen {
|
||||
goto Truncated
|
||||
}
|
||||
encodedLength = contents[0:5]
|
||||
subLen = uint32(contents[1])<<24 |
|
||||
uint32(contents[2])<<16 |
|
||||
uint32(contents[3])<<8 |
|
||||
uint32(contents[4])
|
||||
contents = contents[5:]
|
||||
|
||||
}
|
||||
if subLen > uint32(len(contents)) || subLen == 0 {
|
||||
goto Truncated
|
||||
}
|
||||
subPacket.SubType = contents[0]
|
||||
subPacket.EncodedLength = encodedLength
|
||||
subPacket.Contents = contents[1:subLen]
|
||||
return
|
||||
Truncated:
|
||||
@@ -152,7 +159,9 @@ Truncated:
|
||||
|
||||
func (osp *OpaqueSubpacket) Serialize(w io.Writer) (err error) {
|
||||
buf := make([]byte, 6)
|
||||
n := serializeSubpacketLength(buf, len(osp.Contents)+1)
|
||||
copy(buf, osp.EncodedLength)
|
||||
n := len(osp.EncodedLength)
|
||||
|
||||
buf[n] = osp.SubType
|
||||
if _, err = w.Write(buf[:n+1]); err != nil {
|
||||
return
|
||||
|
||||
+62
-34
@@ -302,21 +302,21 @@ func consumeAll(r io.Reader) (n int64, err error) {
|
||||
type packetType uint8
|
||||
|
||||
const (
|
||||
packetTypeEncryptedKey packetType = 1
|
||||
packetTypeSignature packetType = 2
|
||||
packetTypeSymmetricKeyEncrypted packetType = 3
|
||||
packetTypeOnePassSignature packetType = 4
|
||||
packetTypePrivateKey packetType = 5
|
||||
packetTypePublicKey packetType = 6
|
||||
packetTypePrivateSubkey packetType = 7
|
||||
packetTypeCompressed packetType = 8
|
||||
packetTypeSymmetricallyEncrypted packetType = 9
|
||||
packetTypeLiteralData packetType = 11
|
||||
packetTypeUserId packetType = 13
|
||||
packetTypePublicSubkey packetType = 14
|
||||
packetTypeUserAttribute packetType = 17
|
||||
packetTypeSymmetricallyEncryptedMDC packetType = 18
|
||||
packetTypeAEADEncrypted packetType = 20
|
||||
packetTypeEncryptedKey packetType = 1
|
||||
packetTypeSignature packetType = 2
|
||||
packetTypeSymmetricKeyEncrypted packetType = 3
|
||||
packetTypeOnePassSignature packetType = 4
|
||||
packetTypePrivateKey packetType = 5
|
||||
packetTypePublicKey packetType = 6
|
||||
packetTypePrivateSubkey packetType = 7
|
||||
packetTypeCompressed packetType = 8
|
||||
packetTypeSymmetricallyEncrypted packetType = 9
|
||||
packetTypeLiteralData packetType = 11
|
||||
packetTypeUserId packetType = 13
|
||||
packetTypePublicSubkey packetType = 14
|
||||
packetTypeUserAttribute packetType = 17
|
||||
packetTypeSymmetricallyEncryptedIntegrityProtected packetType = 18
|
||||
packetTypeAEADEncrypted packetType = 20
|
||||
)
|
||||
|
||||
// EncryptedDataPacket holds encrypted data. It is currently implemented by
|
||||
@@ -354,16 +354,16 @@ func Read(r io.Reader) (p Packet, err error) {
|
||||
case packetTypeCompressed:
|
||||
p = new(Compressed)
|
||||
case packetTypeSymmetricallyEncrypted:
|
||||
err = errors.UnsupportedError("Symmetrically encrypted packets without MDC are not supported")
|
||||
p = new(SymmetricallyEncrypted)
|
||||
case packetTypeLiteralData:
|
||||
p = new(LiteralData)
|
||||
case packetTypeUserId:
|
||||
p = new(UserId)
|
||||
case packetTypeUserAttribute:
|
||||
p = new(UserAttribute)
|
||||
case packetTypeSymmetricallyEncryptedMDC:
|
||||
case packetTypeSymmetricallyEncryptedIntegrityProtected:
|
||||
se := new(SymmetricallyEncrypted)
|
||||
se.MDC = true
|
||||
se.IntegrityProtected = true
|
||||
p = se
|
||||
case packetTypeAEADEncrypted:
|
||||
p = new(AEADEncrypted)
|
||||
@@ -384,18 +384,18 @@ func Read(r io.Reader) (p Packet, err error) {
|
||||
type SignatureType uint8
|
||||
|
||||
const (
|
||||
SigTypeBinary SignatureType = 0x00
|
||||
SigTypeText = 0x01
|
||||
SigTypeGenericCert = 0x10
|
||||
SigTypePersonaCert = 0x11
|
||||
SigTypeCasualCert = 0x12
|
||||
SigTypePositiveCert = 0x13
|
||||
SigTypeSubkeyBinding = 0x18
|
||||
SigTypePrimaryKeyBinding = 0x19
|
||||
SigTypeDirectSignature = 0x1F
|
||||
SigTypeKeyRevocation = 0x20
|
||||
SigTypeSubkeyRevocation = 0x28
|
||||
SigTypeCertificationRevocation = 0x30
|
||||
SigTypeBinary SignatureType = 0x00
|
||||
SigTypeText = 0x01
|
||||
SigTypeGenericCert = 0x10
|
||||
SigTypePersonaCert = 0x11
|
||||
SigTypeCasualCert = 0x12
|
||||
SigTypePositiveCert = 0x13
|
||||
SigTypeSubkeyBinding = 0x18
|
||||
SigTypePrimaryKeyBinding = 0x19
|
||||
SigTypeDirectSignature = 0x1F
|
||||
SigTypeKeyRevocation = 0x20
|
||||
SigTypeSubkeyRevocation = 0x28
|
||||
SigTypeCertificationRevocation = 0x30
|
||||
)
|
||||
|
||||
// PublicKeyAlgorithm represents the different public key system specified for
|
||||
@@ -455,6 +455,11 @@ func (cipher CipherFunction) KeySize() int {
|
||||
return algorithm.CipherFunction(cipher).KeySize()
|
||||
}
|
||||
|
||||
// IsSupported returns true if the cipher is supported from the library
|
||||
func (cipher CipherFunction) IsSupported() bool {
|
||||
return algorithm.CipherFunction(cipher).KeySize() > 0
|
||||
}
|
||||
|
||||
// blockSize returns the block size, in bytes, of cipher.
|
||||
func (cipher CipherFunction) blockSize() int {
|
||||
return algorithm.CipherFunction(cipher).BlockSize()
|
||||
@@ -490,15 +495,16 @@ const (
|
||||
|
||||
// AEADMode represents the different Authenticated Encryption with Associated
|
||||
// Data specified for OpenPGP.
|
||||
// See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.6
|
||||
type AEADMode algorithm.AEADMode
|
||||
|
||||
const (
|
||||
AEADModeEAX AEADMode = 1
|
||||
AEADModeOCB AEADMode = 2
|
||||
AEADModeExperimentalGCM AEADMode = 100
|
||||
AEADModeEAX AEADMode = 1
|
||||
AEADModeOCB AEADMode = 2
|
||||
AEADModeGCM AEADMode = 3
|
||||
)
|
||||
|
||||
func (mode AEADMode) NonceLength() int {
|
||||
func (mode AEADMode) IvLength() int {
|
||||
return algorithm.AEADMode(mode).NonceLength()
|
||||
}
|
||||
|
||||
@@ -521,3 +527,25 @@ const (
|
||||
KeyCompromised ReasonForRevocation = 2
|
||||
KeyRetired ReasonForRevocation = 3
|
||||
)
|
||||
|
||||
// Curve is a mapping to supported ECC curves for key generation.
|
||||
// See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#name-curve-specific-wire-formats
|
||||
type Curve string
|
||||
|
||||
const (
|
||||
Curve25519 Curve = "Curve25519"
|
||||
Curve448 Curve = "Curve448"
|
||||
CurveNistP256 Curve = "P256"
|
||||
CurveNistP384 Curve = "P384"
|
||||
CurveNistP521 Curve = "P521"
|
||||
CurveSecP256k1 Curve = "SecP256k1"
|
||||
CurveBrainpoolP256 Curve = "BrainpoolP256"
|
||||
CurveBrainpoolP384 Curve = "BrainpoolP384"
|
||||
CurveBrainpoolP512 Curve = "BrainpoolP512"
|
||||
)
|
||||
|
||||
// TrustLevel represents a trust level per RFC4880 5.2.3.13
|
||||
type TrustLevel uint8
|
||||
|
||||
// TrustAmount represents a trust amount per RFC4880 5.2.3.13
|
||||
type TrustAmount uint8
|
||||
|
||||
+185
-128
@@ -9,27 +9,22 @@ import (
|
||||
"crypto"
|
||||
"crypto/cipher"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/internal/ecc"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/ecdh"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/ecdsa"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/eddsa"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/elgamal"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/internal/encoding"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/s2k"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
// PrivateKey represents a possibly encrypted private key. See RFC 4880,
|
||||
@@ -54,7 +49,7 @@ type PrivateKey struct {
|
||||
s2kParams *s2k.Params
|
||||
}
|
||||
|
||||
//S2KType s2k packet type
|
||||
// S2KType s2k packet type
|
||||
type S2KType uint8
|
||||
|
||||
const (
|
||||
@@ -94,10 +89,9 @@ func NewECDSAPrivateKey(creationTime time.Time, priv *ecdsa.PrivateKey) *Private
|
||||
return pk
|
||||
}
|
||||
|
||||
func NewEdDSAPrivateKey(creationTime time.Time, priv *ed25519.PrivateKey) *PrivateKey {
|
||||
func NewEdDSAPrivateKey(creationTime time.Time, priv *eddsa.PrivateKey) *PrivateKey {
|
||||
pk := new(PrivateKey)
|
||||
pub := priv.Public().(ed25519.PublicKey)
|
||||
pk.PublicKey = *NewEdDSAPublicKey(creationTime, &pub)
|
||||
pk.PublicKey = *NewEdDSAPublicKey(creationTime, &priv.PublicKey)
|
||||
pk.PrivateKey = priv
|
||||
return pk
|
||||
}
|
||||
@@ -111,25 +105,25 @@ func NewECDHPrivateKey(creationTime time.Time, priv *ecdh.PrivateKey) *PrivateKe
|
||||
|
||||
// NewSignerPrivateKey creates a PrivateKey from a crypto.Signer that
|
||||
// implements RSA, ECDSA or EdDSA.
|
||||
func NewSignerPrivateKey(creationTime time.Time, signer crypto.Signer) *PrivateKey {
|
||||
func NewSignerPrivateKey(creationTime time.Time, signer interface{}) *PrivateKey {
|
||||
pk := new(PrivateKey)
|
||||
// In general, the public Keys should be used as pointers. We still
|
||||
// type-switch on the values, for backwards-compatibility.
|
||||
switch pubkey := signer.Public().(type) {
|
||||
case *rsa.PublicKey:
|
||||
pk.PublicKey = *NewRSAPublicKey(creationTime, pubkey)
|
||||
case rsa.PublicKey:
|
||||
pk.PublicKey = *NewRSAPublicKey(creationTime, &pubkey)
|
||||
case *ecdsa.PublicKey:
|
||||
pk.PublicKey = *NewECDSAPublicKey(creationTime, pubkey)
|
||||
case ecdsa.PublicKey:
|
||||
pk.PublicKey = *NewECDSAPublicKey(creationTime, &pubkey)
|
||||
case *ed25519.PublicKey:
|
||||
pk.PublicKey = *NewEdDSAPublicKey(creationTime, pubkey)
|
||||
case ed25519.PublicKey:
|
||||
pk.PublicKey = *NewEdDSAPublicKey(creationTime, &pubkey)
|
||||
switch pubkey := signer.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
pk.PublicKey = *NewRSAPublicKey(creationTime, &pubkey.PublicKey)
|
||||
case rsa.PrivateKey:
|
||||
pk.PublicKey = *NewRSAPublicKey(creationTime, &pubkey.PublicKey)
|
||||
case *ecdsa.PrivateKey:
|
||||
pk.PublicKey = *NewECDSAPublicKey(creationTime, &pubkey.PublicKey)
|
||||
case ecdsa.PrivateKey:
|
||||
pk.PublicKey = *NewECDSAPublicKey(creationTime, &pubkey.PublicKey)
|
||||
case *eddsa.PrivateKey:
|
||||
pk.PublicKey = *NewEdDSAPublicKey(creationTime, &pubkey.PublicKey)
|
||||
case eddsa.PrivateKey:
|
||||
pk.PublicKey = *NewEdDSAPublicKey(creationTime, &pubkey.PublicKey)
|
||||
default:
|
||||
panic("openpgp: unknown crypto.Signer type in NewSignerPrivateKey")
|
||||
panic("openpgp: unknown signer type in NewSignerPrivateKey")
|
||||
}
|
||||
pk.PrivateKey = signer
|
||||
return pk
|
||||
@@ -185,6 +179,9 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) {
|
||||
return
|
||||
}
|
||||
pk.cipher = CipherFunction(buf[0])
|
||||
if pk.cipher != 0 && !pk.cipher.IsSupported() {
|
||||
return errors.UnsupportedError("unsupported cipher function in private key")
|
||||
}
|
||||
pk.s2kParams, err = s2k.ParseIntoParams(r)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -239,6 +236,18 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) {
|
||||
}
|
||||
}
|
||||
if !pk.Encrypted {
|
||||
if len(privateKeyData) < 2 {
|
||||
return errors.StructuralError("truncated private key data")
|
||||
}
|
||||
var sum uint16
|
||||
for i := 0; i < len(privateKeyData)-2; i++ {
|
||||
sum += uint16(privateKeyData[i])
|
||||
}
|
||||
if privateKeyData[len(privateKeyData)-2] != uint8(sum>>8) ||
|
||||
privateKeyData[len(privateKeyData)-1] != uint8(sum) {
|
||||
return errors.StructuralError("private key checksum failure")
|
||||
}
|
||||
privateKeyData = privateKeyData[:len(privateKeyData)-2]
|
||||
return pk.parsePrivateKey(privateKeyData)
|
||||
}
|
||||
|
||||
@@ -294,14 +303,8 @@ func (pk *PrivateKey) Serialize(w io.Writer) (err error) {
|
||||
return err
|
||||
}
|
||||
l = buf.Len()
|
||||
if pk.sha1Checksum {
|
||||
h := sha1.New()
|
||||
h.Write(buf.Bytes())
|
||||
buf.Write(h.Sum(nil))
|
||||
} else {
|
||||
checksum := mod64kHash(buf.Bytes())
|
||||
buf.Write([]byte{byte(checksum >> 8), byte(checksum)})
|
||||
}
|
||||
checksum := mod64kHash(buf.Bytes())
|
||||
buf.Write([]byte{byte(checksum >> 8), byte(checksum)})
|
||||
priv = buf.Bytes()
|
||||
} else {
|
||||
priv, l = pk.encryptedData, len(pk.encryptedData)
|
||||
@@ -353,23 +356,22 @@ func serializeElGamalPrivateKey(w io.Writer, priv *elgamal.PrivateKey) error {
|
||||
}
|
||||
|
||||
func serializeECDSAPrivateKey(w io.Writer, priv *ecdsa.PrivateKey) error {
|
||||
_, err := w.Write(new(encoding.MPI).SetBig(priv.D).EncodedBytes())
|
||||
_, err := w.Write(encoding.NewMPI(priv.MarshalIntegerSecret()).EncodedBytes())
|
||||
return err
|
||||
}
|
||||
|
||||
func serializeEdDSAPrivateKey(w io.Writer, priv *ed25519.PrivateKey) error {
|
||||
keySize := ed25519.PrivateKeySize - ed25519.PublicKeySize
|
||||
_, err := w.Write(encoding.NewMPI((*priv)[:keySize]).EncodedBytes())
|
||||
func serializeEdDSAPrivateKey(w io.Writer, priv *eddsa.PrivateKey) error {
|
||||
_, err := w.Write(encoding.NewMPI(priv.MarshalByteSecret()).EncodedBytes())
|
||||
return err
|
||||
}
|
||||
|
||||
func serializeECDHPrivateKey(w io.Writer, priv *ecdh.PrivateKey) error {
|
||||
_, err := w.Write(encoding.NewMPI(priv.D).EncodedBytes())
|
||||
_, err := w.Write(encoding.NewMPI(priv.MarshalByteSecret()).EncodedBytes())
|
||||
return err
|
||||
}
|
||||
|
||||
// Decrypt decrypts an encrypted private key using a passphrase.
|
||||
func (pk *PrivateKey) Decrypt(passphrase []byte) error {
|
||||
// decrypt decrypts an encrypted private key using a decryption key.
|
||||
func (pk *PrivateKey) decrypt(decryptionKey []byte) error {
|
||||
if pk.Dummy() {
|
||||
return errors.ErrDummyPrivateKey("dummy key found")
|
||||
}
|
||||
@@ -377,9 +379,7 @@ func (pk *PrivateKey) Decrypt(passphrase []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
key := make([]byte, pk.cipher.KeySize())
|
||||
pk.s2k(key, passphrase)
|
||||
block := pk.cipher.new(key)
|
||||
block := pk.cipher.new(decryptionKey)
|
||||
cfb := cipher.NewCFBDecrypter(block, pk.iv)
|
||||
|
||||
data := make([]byte, len(pk.encryptedData))
|
||||
@@ -428,35 +428,79 @@ func (pk *PrivateKey) Decrypt(passphrase []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encrypt encrypts an unencrypted private key using a passphrase.
|
||||
func (pk *PrivateKey) Encrypt(passphrase []byte) error {
|
||||
func (pk *PrivateKey) decryptWithCache(passphrase []byte, keyCache *s2k.Cache) error {
|
||||
if pk.Dummy() {
|
||||
return errors.ErrDummyPrivateKey("dummy key found")
|
||||
}
|
||||
if !pk.Encrypted {
|
||||
return nil
|
||||
}
|
||||
|
||||
key, err := keyCache.GetOrComputeDerivedKey(passphrase, pk.s2kParams, pk.cipher.KeySize())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pk.decrypt(key)
|
||||
}
|
||||
|
||||
// Decrypt decrypts an encrypted private key using a passphrase.
|
||||
func (pk *PrivateKey) Decrypt(passphrase []byte) error {
|
||||
if pk.Dummy() {
|
||||
return errors.ErrDummyPrivateKey("dummy key found")
|
||||
}
|
||||
if !pk.Encrypted {
|
||||
return nil
|
||||
}
|
||||
|
||||
key := make([]byte, pk.cipher.KeySize())
|
||||
pk.s2k(key, passphrase)
|
||||
return pk.decrypt(key)
|
||||
}
|
||||
|
||||
// DecryptPrivateKeys decrypts all encrypted keys with the given config and passphrase.
|
||||
// Avoids recomputation of similar s2k key derivations.
|
||||
func DecryptPrivateKeys(keys []*PrivateKey, passphrase []byte) error {
|
||||
// Create a cache to avoid recomputation of key derviations for the same passphrase.
|
||||
s2kCache := &s2k.Cache{}
|
||||
for _, key := range keys {
|
||||
if key != nil && !key.Dummy() && key.Encrypted {
|
||||
err := key.decryptWithCache(passphrase, s2kCache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// encrypt encrypts an unencrypted private key.
|
||||
func (pk *PrivateKey) encrypt(key []byte, params *s2k.Params, cipherFunction CipherFunction) error {
|
||||
if pk.Dummy() {
|
||||
return errors.ErrDummyPrivateKey("dummy key found")
|
||||
}
|
||||
if pk.Encrypted {
|
||||
return nil
|
||||
}
|
||||
// check if encryptionKey has the correct size
|
||||
if len(key) != cipherFunction.KeySize() {
|
||||
return errors.InvalidArgumentError("supplied encryption key has the wrong size")
|
||||
}
|
||||
|
||||
priv := bytes.NewBuffer(nil)
|
||||
err := pk.serializePrivateKey(priv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Default config of private key encryption
|
||||
pk.cipher = CipherAES256
|
||||
s2kConfig := &s2k.Config{
|
||||
S2KMode: 3, //Iterated
|
||||
S2KCount: 65536,
|
||||
Hash: crypto.SHA256,
|
||||
}
|
||||
|
||||
pk.s2kParams, err = s2k.Generate(rand.Reader, s2kConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privateKeyBytes := priv.Bytes()
|
||||
key := make([]byte, pk.cipher.KeySize())
|
||||
|
||||
pk.sha1Checksum = true
|
||||
pk.cipher = cipherFunction
|
||||
pk.s2kParams = params
|
||||
pk.s2k, err = pk.s2kParams.Function()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pk.s2k(key, passphrase)
|
||||
}
|
||||
|
||||
privateKeyBytes := priv.Bytes()
|
||||
pk.sha1Checksum = true
|
||||
block := pk.cipher.new(key)
|
||||
pk.iv = make([]byte, pk.cipher.blockSize())
|
||||
_, err = rand.Read(pk.iv)
|
||||
@@ -487,6 +531,62 @@ func (pk *PrivateKey) Encrypt(passphrase []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// EncryptWithConfig encrypts an unencrypted private key using the passphrase and the config.
|
||||
func (pk *PrivateKey) EncryptWithConfig(passphrase []byte, config *Config) error {
|
||||
params, err := s2k.Generate(config.Random(), config.S2K())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Derive an encryption key with the configured s2k function.
|
||||
key := make([]byte, config.Cipher().KeySize())
|
||||
s2k, err := params.Function()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s2k(key, passphrase)
|
||||
// Encrypt the private key with the derived encryption key.
|
||||
return pk.encrypt(key, params, config.Cipher())
|
||||
}
|
||||
|
||||
// EncryptPrivateKeys encrypts all unencrypted keys with the given config and passphrase.
|
||||
// Only derives one key from the passphrase, which is then used to encrypt each key.
|
||||
func EncryptPrivateKeys(keys []*PrivateKey, passphrase []byte, config *Config) error {
|
||||
params, err := s2k.Generate(config.Random(), config.S2K())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Derive an encryption key with the configured s2k function.
|
||||
encryptionKey := make([]byte, config.Cipher().KeySize())
|
||||
s2k, err := params.Function()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s2k(encryptionKey, passphrase)
|
||||
for _, key := range keys {
|
||||
if key != nil && !key.Dummy() && !key.Encrypted {
|
||||
err = key.encrypt(encryptionKey, params, config.Cipher())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encrypt encrypts an unencrypted private key using a passphrase.
|
||||
func (pk *PrivateKey) Encrypt(passphrase []byte) error {
|
||||
// Default config of private key encryption
|
||||
config := &Config{
|
||||
S2KConfig: &s2k.Config{
|
||||
S2KMode: s2k.IteratedSaltedS2K,
|
||||
S2KCount: 65536,
|
||||
Hash: crypto.SHA256,
|
||||
} ,
|
||||
DefaultCipher: CipherAES256,
|
||||
}
|
||||
return pk.EncryptWithConfig(passphrase, config)
|
||||
}
|
||||
|
||||
func (pk *PrivateKey) serializePrivateKey(w io.Writer) (err error) {
|
||||
switch priv := pk.PrivateKey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
@@ -497,7 +597,7 @@ func (pk *PrivateKey) serializePrivateKey(w io.Writer) (err error) {
|
||||
err = serializeElGamalPrivateKey(w, priv)
|
||||
case *ecdsa.PrivateKey:
|
||||
err = serializeECDSAPrivateKey(w, priv)
|
||||
case *ed25519.PrivateKey:
|
||||
case *eddsa.PrivateKey:
|
||||
err = serializeEdDSAPrivateKey(w, priv)
|
||||
case *ecdh.PrivateKey:
|
||||
err = serializeECDHPrivateKey(w, priv)
|
||||
@@ -601,8 +701,7 @@ func (pk *PrivateKey) parseElGamalPrivateKey(data []byte) (err error) {
|
||||
|
||||
func (pk *PrivateKey) parseECDSAPrivateKey(data []byte) (err error) {
|
||||
ecdsaPub := pk.PublicKey.PublicKey.(*ecdsa.PublicKey)
|
||||
ecdsaPriv := new(ecdsa.PrivateKey)
|
||||
ecdsaPriv.PublicKey = *ecdsaPub
|
||||
ecdsaPriv := ecdsa.NewPrivateKey(*ecdsaPub)
|
||||
|
||||
buf := bytes.NewBuffer(data)
|
||||
d := new(encoding.MPI)
|
||||
@@ -610,8 +709,10 @@ func (pk *PrivateKey) parseECDSAPrivateKey(data []byte) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
ecdsaPriv.D = new(big.Int).SetBytes(d.Bytes())
|
||||
if err := validateECDSAParameters(ecdsaPriv); err != nil {
|
||||
if err := ecdsaPriv.UnmarshalIntegerSecret(d.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ecdsa.Validate(ecdsaPriv); err != nil {
|
||||
return err
|
||||
}
|
||||
pk.PrivateKey = ecdsaPriv
|
||||
@@ -621,8 +722,7 @@ func (pk *PrivateKey) parseECDSAPrivateKey(data []byte) (err error) {
|
||||
|
||||
func (pk *PrivateKey) parseECDHPrivateKey(data []byte) (err error) {
|
||||
ecdhPub := pk.PublicKey.PublicKey.(*ecdh.PublicKey)
|
||||
ecdhPriv := new(ecdh.PrivateKey)
|
||||
ecdhPriv.PublicKey = *ecdhPub
|
||||
ecdhPriv := ecdh.NewPrivateKey(*ecdhPub)
|
||||
|
||||
buf := bytes.NewBuffer(data)
|
||||
d := new(encoding.MPI)
|
||||
@@ -630,18 +730,23 @@ func (pk *PrivateKey) parseECDHPrivateKey(data []byte) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
ecdhPriv.D = d.Bytes()
|
||||
if err := validateECDHParameters(ecdhPriv); err != nil {
|
||||
if err := ecdhPriv.UnmarshalByteSecret(d.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ecdh.Validate(ecdhPriv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pk.PrivateKey = ecdhPriv
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pk *PrivateKey) parseEdDSAPrivateKey(data []byte) (err error) {
|
||||
eddsaPub := pk.PublicKey.PublicKey.(*ed25519.PublicKey)
|
||||
eddsaPriv := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
|
||||
eddsaPub := pk.PublicKey.PublicKey.(*eddsa.PublicKey)
|
||||
eddsaPriv := eddsa.NewPrivateKey(*eddsaPub)
|
||||
eddsaPriv.PublicKey = *eddsaPub
|
||||
|
||||
buf := bytes.NewBuffer(data)
|
||||
d := new(encoding.MPI)
|
||||
@@ -649,64 +754,16 @@ func (pk *PrivateKey) parseEdDSAPrivateKey(data []byte) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
priv := d.Bytes()
|
||||
copy(eddsaPriv[32-len(priv):32], priv)
|
||||
copy(eddsaPriv[32:], (*eddsaPub)[:])
|
||||
if err := validateEdDSAParameters(&eddsaPriv); err != nil {
|
||||
if err = eddsaPriv.UnmarshalByteSecret(d.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
pk.PrivateKey = &eddsaPriv
|
||||
|
||||
return nil
|
||||
}
|
||||
if err := eddsa.Validate(eddsaPriv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func validateECDSAParameters(priv *ecdsa.PrivateKey) error {
|
||||
return validateCommonECC(priv.Curve, priv.D.Bytes(), priv.X, priv.Y)
|
||||
}
|
||||
pk.PrivateKey = eddsaPriv
|
||||
|
||||
func validateECDHParameters(priv *ecdh.PrivateKey) error {
|
||||
if priv.CurveType != ecc.Curve25519 {
|
||||
return validateCommonECC(priv.Curve, priv.D, priv.X, priv.Y)
|
||||
}
|
||||
// Handle Curve25519
|
||||
Q := priv.X.Bytes()[1:]
|
||||
var d [32]byte
|
||||
// Copy reversed d
|
||||
l := len(priv.D)
|
||||
for i := 0; i < l; i++ {
|
||||
d[i] = priv.D[l-i-1]
|
||||
}
|
||||
var expectedQ [32]byte
|
||||
curve25519.ScalarBaseMult(&expectedQ, &d)
|
||||
if !bytes.Equal(Q, expectedQ[:]) {
|
||||
return errors.KeyInvalidError("ECDH curve25519: invalid point")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCommonECC(curve elliptic.Curve, d []byte, X, Y *big.Int) error {
|
||||
// the public point should not be at infinity (0,0)
|
||||
zero := new(big.Int)
|
||||
if X.Cmp(zero) == 0 && Y.Cmp(zero) == 0 {
|
||||
return errors.KeyInvalidError(fmt.Sprintf("ecc (%s): infinity point", curve.Params().Name))
|
||||
}
|
||||
// re-derive the public point Q' = (X,Y) = dG
|
||||
// to compare to declared Q in public key
|
||||
expectedX, expectedY := curve.ScalarBaseMult(d)
|
||||
if X.Cmp(expectedX) != 0 || Y.Cmp(expectedY) != 0 {
|
||||
return errors.KeyInvalidError(fmt.Sprintf("ecc (%s): invalid point", curve.Params().Name))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateEdDSAParameters(priv *ed25519.PrivateKey) error {
|
||||
// In EdDSA, the serialized public point is stored as part of private key (together with the seed),
|
||||
// hence we can re-derive the key from the seed
|
||||
seed := priv.Seed()
|
||||
expectedPriv := ed25519.NewKeyFromSeed(seed)
|
||||
if !bytes.Equal(*priv, expectedPriv) {
|
||||
return errors.KeyInvalidError("eddsa: invalid point")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+57
-76
@@ -7,8 +7,6 @@ package packet
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
@@ -22,12 +20,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/ecdh"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/ecdsa"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/eddsa"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/elgamal"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/internal/ecc"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/internal/encoding"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
type kdfHashFunction byte
|
||||
@@ -124,10 +123,10 @@ func NewECDSAPublicKey(creationTime time.Time, pub *ecdsa.PublicKey) *PublicKey
|
||||
CreationTime: creationTime,
|
||||
PubKeyAlgo: PubKeyAlgoECDSA,
|
||||
PublicKey: pub,
|
||||
p: encoding.NewMPI(elliptic.Marshal(pub.Curve, pub.X, pub.Y)),
|
||||
p: encoding.NewMPI(pub.MarshalPoint()),
|
||||
}
|
||||
|
||||
curveInfo := ecc.FindByCurve(pub.Curve)
|
||||
curveInfo := ecc.FindByCurve(pub.GetCurve())
|
||||
if curveInfo == nil {
|
||||
panic("unknown elliptic curve")
|
||||
}
|
||||
@@ -138,39 +137,29 @@ func NewECDSAPublicKey(creationTime time.Time, pub *ecdsa.PublicKey) *PublicKey
|
||||
|
||||
func NewECDHPublicKey(creationTime time.Time, pub *ecdh.PublicKey) *PublicKey {
|
||||
var pk *PublicKey
|
||||
var curveInfo *ecc.CurveInfo
|
||||
var kdf = encoding.NewOID([]byte{0x1, pub.Hash.Id(), pub.Cipher.Id()})
|
||||
if pub.CurveType == ecc.Curve25519 {
|
||||
pk = &PublicKey{
|
||||
Version: 4,
|
||||
CreationTime: creationTime,
|
||||
PubKeyAlgo: PubKeyAlgoECDH,
|
||||
PublicKey: pub,
|
||||
p: encoding.NewMPI(pub.X.Bytes()),
|
||||
kdf: kdf,
|
||||
}
|
||||
curveInfo = ecc.FindByName("Curve25519")
|
||||
} else {
|
||||
pk = &PublicKey{
|
||||
Version: 4,
|
||||
CreationTime: creationTime,
|
||||
PubKeyAlgo: PubKeyAlgoECDH,
|
||||
PublicKey: pub,
|
||||
p: encoding.NewMPI(elliptic.Marshal(pub.Curve, pub.X, pub.Y)),
|
||||
kdf: kdf,
|
||||
}
|
||||
curveInfo = ecc.FindByCurve(pub.Curve)
|
||||
pk = &PublicKey{
|
||||
Version: 4,
|
||||
CreationTime: creationTime,
|
||||
PubKeyAlgo: PubKeyAlgoECDH,
|
||||
PublicKey: pub,
|
||||
p: encoding.NewMPI(pub.MarshalPoint()),
|
||||
kdf: kdf,
|
||||
}
|
||||
|
||||
curveInfo := ecc.FindByCurve(pub.GetCurve())
|
||||
|
||||
if curveInfo == nil {
|
||||
panic("unknown elliptic curve")
|
||||
}
|
||||
|
||||
pk.oid = curveInfo.Oid
|
||||
pk.setFingerprintAndKeyId()
|
||||
return pk
|
||||
}
|
||||
|
||||
func NewEdDSAPublicKey(creationTime time.Time, pub *ed25519.PublicKey) *PublicKey {
|
||||
curveInfo := ecc.FindByName("Ed25519")
|
||||
func NewEdDSAPublicKey(creationTime time.Time, pub *eddsa.PublicKey) *PublicKey {
|
||||
curveInfo := ecc.FindByCurve(pub.GetCurve())
|
||||
pk := &PublicKey{
|
||||
Version: 4,
|
||||
CreationTime: creationTime,
|
||||
@@ -178,7 +167,7 @@ func NewEdDSAPublicKey(creationTime time.Time, pub *ed25519.PublicKey) *PublicKe
|
||||
PublicKey: pub,
|
||||
oid: curveInfo.Oid,
|
||||
// Native point format, see draft-koch-eddsa-for-openpgp-04, Appendix B
|
||||
p: encoding.NewMPI(append([]byte{0x40}, *pub...)),
|
||||
p: encoding.NewMPI(pub.MarshalPoint()),
|
||||
}
|
||||
|
||||
pk.setFingerprintAndKeyId()
|
||||
@@ -340,17 +329,20 @@ func (pk *PublicKey) parseECDSA(r io.Reader) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
var c elliptic.Curve
|
||||
curveInfo := ecc.FindByOid(pk.oid)
|
||||
if curveInfo == nil || curveInfo.SigAlgorithm != ecc.ECDSA {
|
||||
if curveInfo == nil {
|
||||
return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid))
|
||||
}
|
||||
|
||||
c, ok := curveInfo.Curve.(ecc.ECDSACurve)
|
||||
if !ok {
|
||||
return errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", pk.oid))
|
||||
}
|
||||
c = curveInfo.Curve
|
||||
x, y := elliptic.Unmarshal(c, pk.p.Bytes())
|
||||
if x == nil {
|
||||
return errors.UnsupportedError("failed to parse EC point")
|
||||
}
|
||||
pk.PublicKey = &ecdsa.PublicKey{Curve: c, X: x, Y: y}
|
||||
|
||||
ecdsaKey := ecdsa.NewPublicKey(c)
|
||||
err = ecdsaKey.UnmarshalPoint(pk.p.Bytes())
|
||||
pk.PublicKey = ecdsaKey
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -371,24 +363,16 @@ func (pk *PublicKey) parseECDH(r io.Reader) (err error) {
|
||||
}
|
||||
|
||||
curveInfo := ecc.FindByOid(pk.oid)
|
||||
|
||||
if curveInfo == nil {
|
||||
return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid))
|
||||
}
|
||||
|
||||
c, ok := curveInfo.Curve.(ecc.ECDHCurve)
|
||||
if !ok {
|
||||
return errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", pk.oid))
|
||||
}
|
||||
|
||||
c := curveInfo.Curve
|
||||
cType := curveInfo.CurveType
|
||||
|
||||
var x, y *big.Int
|
||||
if cType == ecc.Curve25519 {
|
||||
x = new(big.Int)
|
||||
x.SetBytes(pk.p.Bytes())
|
||||
} else {
|
||||
x, y = elliptic.Unmarshal(c, pk.p.Bytes())
|
||||
}
|
||||
if x == nil {
|
||||
return errors.UnsupportedError("failed to parse EC point")
|
||||
}
|
||||
|
||||
if kdfLen := len(pk.kdf.Bytes()); kdfLen < 3 {
|
||||
return errors.UnsupportedError("unsupported ECDH KDF length: " + strconv.Itoa(kdfLen))
|
||||
}
|
||||
@@ -404,16 +388,10 @@ func (pk *PublicKey) parseECDH(r io.Reader) (err error) {
|
||||
return errors.UnsupportedError("unsupported ECDH KDF cipher: " + strconv.Itoa(int(pk.kdf.Bytes()[2])))
|
||||
}
|
||||
|
||||
pk.PublicKey = &ecdh.PublicKey{
|
||||
CurveType: cType,
|
||||
Curve: c,
|
||||
X: x,
|
||||
Y: y,
|
||||
KDF: ecdh.KDF{
|
||||
Hash: kdfHash,
|
||||
Cipher: kdfCipher,
|
||||
},
|
||||
}
|
||||
ecdhKey := ecdh.NewPublicKey(c, kdfHash, kdfCipher)
|
||||
err = ecdhKey.UnmarshalPoint(pk.p.Bytes())
|
||||
pk.PublicKey = ecdhKey
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -423,26 +401,37 @@ func (pk *PublicKey) parseEdDSA(r io.Reader) (err error) {
|
||||
return
|
||||
}
|
||||
curveInfo := ecc.FindByOid(pk.oid)
|
||||
if curveInfo == nil || curveInfo.SigAlgorithm != ecc.EdDSA {
|
||||
if curveInfo == nil {
|
||||
return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid))
|
||||
}
|
||||
|
||||
c, ok := curveInfo.Curve.(ecc.EdDSACurve)
|
||||
if !ok {
|
||||
return errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", pk.oid))
|
||||
}
|
||||
|
||||
pk.p = new(encoding.MPI)
|
||||
if _, err = pk.p.ReadFrom(r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
eddsa := make(ed25519.PublicKey, ed25519.PublicKeySize)
|
||||
if len(pk.p.Bytes()) == 0 {
|
||||
return errors.StructuralError("empty EdDSA public key")
|
||||
}
|
||||
|
||||
pub := eddsa.NewPublicKey(c)
|
||||
|
||||
switch flag := pk.p.Bytes()[0]; flag {
|
||||
case 0x04:
|
||||
// TODO: see _grcy_ecc_eddsa_ensure_compact in grcypt
|
||||
return errors.UnsupportedError("unsupported EdDSA compression: " + strconv.Itoa(int(flag)))
|
||||
case 0x40:
|
||||
copy(eddsa[:], pk.p.Bytes()[1:])
|
||||
err = pub.UnmarshalPoint(pk.p.Bytes())
|
||||
default:
|
||||
return errors.UnsupportedError("unsupported EdDSA compression: " + strconv.Itoa(int(flag)))
|
||||
}
|
||||
|
||||
pk.PublicKey = &eddsa
|
||||
pk.PublicKey = pub
|
||||
return
|
||||
}
|
||||
|
||||
@@ -611,7 +600,7 @@ func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err erro
|
||||
}
|
||||
signed.Write(sig.HashSuffix)
|
||||
hashBytes := signed.Sum(nil)
|
||||
if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
|
||||
if sig.Version == 5 && (hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1]) {
|
||||
return errors.SignatureError("hash tag doesn't match")
|
||||
}
|
||||
|
||||
@@ -645,16 +634,8 @@ func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err erro
|
||||
}
|
||||
return nil
|
||||
case PubKeyAlgoEdDSA:
|
||||
eddsaPublicKey := pk.PublicKey.(*ed25519.PublicKey)
|
||||
|
||||
sigR := sig.EdDSASigR.Bytes()
|
||||
sigS := sig.EdDSASigS.Bytes()
|
||||
|
||||
eddsaSig := make([]byte, ed25519.SignatureSize)
|
||||
copy(eddsaSig[32-len(sigR):32], sigR)
|
||||
copy(eddsaSig[64-len(sigS):], sigS)
|
||||
|
||||
if !ed25519.Verify(*eddsaPublicKey, hashBytes, eddsaSig) {
|
||||
eddsaPublicKey := pk.PublicKey.(*eddsa.PublicKey)
|
||||
if !eddsa.Verify(eddsaPublicKey, hashBytes, sig.EdDSASigR.Bytes(), sig.EdDSASigS.Bytes()) {
|
||||
return errors.SignatureError("EdDSA verification failure")
|
||||
}
|
||||
return nil
|
||||
|
||||
+10
-2
@@ -44,9 +44,17 @@ func (r *Reader) Next() (p Packet, err error) {
|
||||
continue
|
||||
}
|
||||
// TODO: Add strict mode that rejects unknown packets, instead of ignoring them.
|
||||
if _, ok := err.(errors.UnknownPacketTypeError); !ok {
|
||||
return nil, err
|
||||
if _, ok := err.(errors.UnknownPacketTypeError); ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := err.(errors.UnsupportedError); ok {
|
||||
switch p.(type) {
|
||||
case *SymmetricallyEncrypted, *AEADEncrypted, *Compressed, *LiteralData:
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, io.EOF
|
||||
|
||||
+166
-95
@@ -8,18 +8,17 @@ import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"encoding/asn1"
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
"io"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/ecdsa"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/eddsa"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/internal/encoding"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/s2k"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -67,10 +66,24 @@ type Signature struct {
|
||||
|
||||
SigLifetimeSecs, KeyLifetimeSecs *uint32
|
||||
PreferredSymmetric, PreferredHash, PreferredCompression []uint8
|
||||
PreferredAEAD []uint8
|
||||
PreferredCipherSuites [][2]uint8
|
||||
IssuerKeyId *uint64
|
||||
IssuerFingerprint []byte
|
||||
SignerUserId *string
|
||||
IsPrimaryId *bool
|
||||
Notations []*Notation
|
||||
|
||||
// TrustLevel and TrustAmount can be set by the signer to assert that
|
||||
// the key is not only valid but also trustworthy at the specified
|
||||
// level.
|
||||
// See RFC 4880, section 5.2.3.13 for details.
|
||||
TrustLevel TrustLevel
|
||||
TrustAmount TrustAmount
|
||||
|
||||
// TrustRegularExpression can be used in conjunction with trust Signature
|
||||
// packets to limit the scope of the trust that is extended.
|
||||
// See RFC 4880, section 5.2.3.14 for details.
|
||||
TrustRegularExpression *string
|
||||
|
||||
// PolicyURI can be set to the URI of a document that describes the
|
||||
// policy under which the signature was issued. See RFC 4880, section
|
||||
@@ -89,8 +102,8 @@ type Signature struct {
|
||||
|
||||
// In a self-signature, these flags are set there is a features subpacket
|
||||
// indicating that the issuer implementation supports these features
|
||||
// (section 5.2.5.25).
|
||||
MDC, AEAD, V5Keys bool
|
||||
// see https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#features-subpacket
|
||||
SEIPDv1, SEIPDv2 bool
|
||||
|
||||
// EmbeddedSignature, if non-nil, is a signature of the parent key, by
|
||||
// this key. This prevents an attacker from claiming another's signing
|
||||
@@ -126,7 +139,13 @@ func (sig *Signature) parse(r io.Reader) (err error) {
|
||||
}
|
||||
|
||||
var ok bool
|
||||
sig.Hash, ok = s2k.HashIdToHash(buf[2])
|
||||
|
||||
if sig.Version < 5 {
|
||||
sig.Hash, ok = algorithm.HashIdToHashWithSha1(buf[2])
|
||||
} else {
|
||||
sig.Hash, ok = algorithm.HashIdToHash(buf[2])
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2])))
|
||||
}
|
||||
@@ -137,7 +156,11 @@ func (sig *Signature) parse(r io.Reader) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sig.buildHashSuffix(hashedSubpackets)
|
||||
err = sig.buildHashSuffix(hashedSubpackets)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = parseSignatureSubpackets(sig, hashedSubpackets, true)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -221,19 +244,23 @@ type signatureSubpacketType uint8
|
||||
const (
|
||||
creationTimeSubpacket signatureSubpacketType = 2
|
||||
signatureExpirationSubpacket signatureSubpacketType = 3
|
||||
trustSubpacket signatureSubpacketType = 5
|
||||
regularExpressionSubpacket signatureSubpacketType = 6
|
||||
keyExpirationSubpacket signatureSubpacketType = 9
|
||||
prefSymmetricAlgosSubpacket signatureSubpacketType = 11
|
||||
issuerSubpacket signatureSubpacketType = 16
|
||||
notationDataSubpacket signatureSubpacketType = 20
|
||||
prefHashAlgosSubpacket signatureSubpacketType = 21
|
||||
prefCompressionSubpacket signatureSubpacketType = 22
|
||||
primaryUserIdSubpacket signatureSubpacketType = 25
|
||||
policyUriSubpacket signatureSubpacketType = 26
|
||||
keyFlagsSubpacket signatureSubpacketType = 27
|
||||
signerUserIdSubpacket signatureSubpacketType = 28
|
||||
reasonForRevocationSubpacket signatureSubpacketType = 29
|
||||
featuresSubpacket signatureSubpacketType = 30
|
||||
embeddedSignatureSubpacket signatureSubpacketType = 32
|
||||
issuerFingerprintSubpacket signatureSubpacketType = 33
|
||||
prefAeadAlgosSubpacket signatureSubpacketType = 34
|
||||
prefCipherSuitesSubpacket signatureSubpacketType = 39
|
||||
)
|
||||
|
||||
// parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1.
|
||||
@@ -244,6 +271,10 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
||||
packetType signatureSubpacketType
|
||||
isCritical bool
|
||||
)
|
||||
if len(subpacket) == 0 {
|
||||
err = errors.StructuralError("zero length signature subpacket")
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case subpacket[0] < 192:
|
||||
length = uint32(subpacket[0])
|
||||
@@ -277,12 +308,14 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
||||
isCritical = subpacket[0]&0x80 == 0x80
|
||||
subpacket = subpacket[1:]
|
||||
sig.rawSubpackets = append(sig.rawSubpackets, outputSubpacket{isHashed, packetType, isCritical, subpacket})
|
||||
if !isHashed &&
|
||||
packetType != issuerSubpacket &&
|
||||
packetType != issuerFingerprintSubpacket &&
|
||||
packetType != embeddedSignatureSubpacket {
|
||||
return
|
||||
}
|
||||
switch packetType {
|
||||
case creationTimeSubpacket:
|
||||
if !isHashed {
|
||||
err = errors.StructuralError("signature creation time in non-hashed area")
|
||||
return
|
||||
}
|
||||
if len(subpacket) != 4 {
|
||||
err = errors.StructuralError("signature creation time not four bytes")
|
||||
return
|
||||
@@ -291,20 +324,35 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
||||
sig.CreationTime = time.Unix(int64(t), 0)
|
||||
case signatureExpirationSubpacket:
|
||||
// Signature expiration time, section 5.2.3.10
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
if len(subpacket) != 4 {
|
||||
err = errors.StructuralError("expiration subpacket with bad length")
|
||||
return
|
||||
}
|
||||
sig.SigLifetimeSecs = new(uint32)
|
||||
*sig.SigLifetimeSecs = binary.BigEndian.Uint32(subpacket)
|
||||
case keyExpirationSubpacket:
|
||||
// Key expiration time, section 5.2.3.6
|
||||
if !isHashed {
|
||||
case trustSubpacket:
|
||||
if len(subpacket) != 2 {
|
||||
err = errors.StructuralError("trust subpacket with bad length")
|
||||
return
|
||||
}
|
||||
// Trust level and amount, section 5.2.3.13
|
||||
sig.TrustLevel = TrustLevel(subpacket[0])
|
||||
sig.TrustAmount = TrustAmount(subpacket[1])
|
||||
case regularExpressionSubpacket:
|
||||
if len(subpacket) == 0 {
|
||||
err = errors.StructuralError("regexp subpacket with bad length")
|
||||
return
|
||||
}
|
||||
// Trust regular expression, section 5.2.3.14
|
||||
// RFC specifies the string should be null-terminated; remove a null byte from the end
|
||||
if subpacket[len(subpacket)-1] != 0x00 {
|
||||
err = errors.StructuralError("expected regular expression to be null-terminated")
|
||||
return
|
||||
}
|
||||
trustRegularExpression := string(subpacket[:len(subpacket)-1])
|
||||
sig.TrustRegularExpression = &trustRegularExpression
|
||||
case keyExpirationSubpacket:
|
||||
// Key expiration time, section 5.2.3.6
|
||||
if len(subpacket) != 4 {
|
||||
err = errors.StructuralError("key expiration subpacket with bad length")
|
||||
return
|
||||
@@ -313,41 +361,52 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
||||
*sig.KeyLifetimeSecs = binary.BigEndian.Uint32(subpacket)
|
||||
case prefSymmetricAlgosSubpacket:
|
||||
// Preferred symmetric algorithms, section 5.2.3.7
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
sig.PreferredSymmetric = make([]byte, len(subpacket))
|
||||
copy(sig.PreferredSymmetric, subpacket)
|
||||
case issuerSubpacket:
|
||||
// Issuer, section 5.2.3.5
|
||||
if sig.Version > 4 {
|
||||
err = errors.StructuralError("issuer subpacket found in v5 key")
|
||||
return
|
||||
}
|
||||
// Issuer, section 5.2.3.5
|
||||
if len(subpacket) != 8 {
|
||||
err = errors.StructuralError("issuer subpacket with bad length")
|
||||
return
|
||||
}
|
||||
sig.IssuerKeyId = new(uint64)
|
||||
*sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket)
|
||||
case prefHashAlgosSubpacket:
|
||||
// Preferred hash algorithms, section 5.2.3.8
|
||||
if !isHashed {
|
||||
case notationDataSubpacket:
|
||||
// Notation data, section 5.2.3.16
|
||||
if len(subpacket) < 8 {
|
||||
err = errors.StructuralError("notation data subpacket with bad length")
|
||||
return
|
||||
}
|
||||
|
||||
nameLength := uint32(subpacket[4])<<8 | uint32(subpacket[5])
|
||||
valueLength := uint32(subpacket[6])<<8 | uint32(subpacket[7])
|
||||
if len(subpacket) != int(nameLength)+int(valueLength)+8 {
|
||||
err = errors.StructuralError("notation data subpacket with bad length")
|
||||
return
|
||||
}
|
||||
|
||||
notation := Notation{
|
||||
IsHumanReadable: (subpacket[0] & 0x80) == 0x80,
|
||||
Name: string(subpacket[8:(nameLength + 8)]),
|
||||
Value: subpacket[(nameLength + 8):(valueLength + nameLength + 8)],
|
||||
IsCritical: isCritical,
|
||||
}
|
||||
|
||||
sig.Notations = append(sig.Notations, ¬ation)
|
||||
case prefHashAlgosSubpacket:
|
||||
// Preferred hash algorithms, section 5.2.3.8
|
||||
sig.PreferredHash = make([]byte, len(subpacket))
|
||||
copy(sig.PreferredHash, subpacket)
|
||||
case prefCompressionSubpacket:
|
||||
// Preferred compression algorithms, section 5.2.3.9
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
sig.PreferredCompression = make([]byte, len(subpacket))
|
||||
copy(sig.PreferredCompression, subpacket)
|
||||
case primaryUserIdSubpacket:
|
||||
// Primary User ID, section 5.2.3.19
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
if len(subpacket) != 1 {
|
||||
err = errors.StructuralError("primary user id subpacket with bad length")
|
||||
return
|
||||
@@ -358,9 +417,6 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
||||
}
|
||||
case keyFlagsSubpacket:
|
||||
// Key flags, section 5.2.3.21
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
if len(subpacket) == 0 {
|
||||
err = errors.StructuralError("empty key flags subpacket")
|
||||
return
|
||||
@@ -387,11 +443,11 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
||||
if subpacket[0]&KeyFlagGroupKey != 0 {
|
||||
sig.FlagGroupKey = true
|
||||
}
|
||||
case signerUserIdSubpacket:
|
||||
userId := string(subpacket)
|
||||
sig.SignerUserId = &userId
|
||||
case reasonForRevocationSubpacket:
|
||||
// Reason For Revocation, section 5.2.3.23
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
if len(subpacket) == 0 {
|
||||
err = errors.StructuralError("empty revocation reason subpacket")
|
||||
return
|
||||
@@ -403,18 +459,13 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
||||
// Features subpacket, section 5.2.3.24 specifies a very general
|
||||
// mechanism for OpenPGP implementations to signal support for new
|
||||
// features.
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
if len(subpacket) > 0 {
|
||||
if subpacket[0]&0x01 != 0 {
|
||||
sig.MDC = true
|
||||
sig.SEIPDv1 = true
|
||||
}
|
||||
if subpacket[0]&0x02 != 0 {
|
||||
sig.AEAD = true
|
||||
}
|
||||
if subpacket[0]&0x04 != 0 {
|
||||
sig.V5Keys = true
|
||||
// 0x02 and 0x04 are reserved
|
||||
if subpacket[0]&0x08 != 0 {
|
||||
sig.SEIPDv2 = true
|
||||
}
|
||||
}
|
||||
case embeddedSignatureSubpacket:
|
||||
@@ -437,11 +488,12 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
||||
}
|
||||
case policyUriSubpacket:
|
||||
// Policy URI, section 5.2.3.20
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
sig.PolicyURI = string(subpacket)
|
||||
case issuerFingerprintSubpacket:
|
||||
if len(subpacket) == 0 {
|
||||
err = errors.StructuralError("empty issuer fingerprint subpacket")
|
||||
return
|
||||
}
|
||||
v, l := subpacket[0], len(subpacket[1:])
|
||||
if v == 5 && l != 32 || v != 5 && l != 20 {
|
||||
return nil, errors.StructuralError("bad fingerprint length")
|
||||
@@ -454,13 +506,19 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
||||
} else {
|
||||
*sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket[13:21])
|
||||
}
|
||||
case prefAeadAlgosSubpacket:
|
||||
// Preferred symmetric algorithms, section 5.2.3.8
|
||||
if !isHashed {
|
||||
case prefCipherSuitesSubpacket:
|
||||
// Preferred AEAD cipher suites
|
||||
// See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#name-preferred-aead-ciphersuites
|
||||
if len(subpacket)%2 != 0 {
|
||||
err = errors.StructuralError("invalid aead cipher suite length")
|
||||
return
|
||||
}
|
||||
sig.PreferredAEAD = make([]byte, len(subpacket))
|
||||
copy(sig.PreferredAEAD, subpacket)
|
||||
|
||||
sig.PreferredCipherSuites = make([][2]byte, len(subpacket)/2)
|
||||
|
||||
for i := 0; i < len(subpacket)/2; i++ {
|
||||
sig.PreferredCipherSuites[i] = [2]uint8{subpacket[2*i], subpacket[2*i+1]}
|
||||
}
|
||||
default:
|
||||
if isCritical {
|
||||
err = errors.UnsupportedError("unknown critical signature subpacket type " + strconv.Itoa(int(packetType)))
|
||||
@@ -558,7 +616,15 @@ func (sig *Signature) SigExpired(currentTime time.Time) bool {
|
||||
|
||||
// buildHashSuffix constructs the HashSuffix member of sig in preparation for signing.
|
||||
func (sig *Signature) buildHashSuffix(hashedSubpackets []byte) (err error) {
|
||||
hash, ok := s2k.HashToHashId(sig.Hash)
|
||||
var hashId byte
|
||||
var ok bool
|
||||
|
||||
if sig.Version < 5 {
|
||||
hashId, ok = algorithm.HashToHashIdWithSha1(sig.Hash)
|
||||
} else {
|
||||
hashId, ok = algorithm.HashToHashId(sig.Hash)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
sig.HashSuffix = nil
|
||||
return errors.InvalidArgumentError("hash cannot be represented in OpenPGP: " + strconv.Itoa(int(sig.Hash)))
|
||||
@@ -568,7 +634,7 @@ func (sig *Signature) buildHashSuffix(hashedSubpackets []byte) (err error) {
|
||||
uint8(sig.Version),
|
||||
uint8(sig.SigType),
|
||||
uint8(sig.PubKeyAlgo),
|
||||
uint8(hash),
|
||||
uint8(hashId),
|
||||
uint8(len(hashedSubpackets) >> 8),
|
||||
uint8(len(hashedSubpackets)),
|
||||
})
|
||||
@@ -649,26 +715,19 @@ func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err e
|
||||
sig.DSASigS = new(encoding.MPI).SetBig(s)
|
||||
}
|
||||
case PubKeyAlgoECDSA:
|
||||
var r, s *big.Int
|
||||
if pk, ok := priv.PrivateKey.(*ecdsa.PrivateKey); ok {
|
||||
// direct support, avoid asn1 wrapping/unwrapping
|
||||
r, s, err = ecdsa.Sign(config.Random(), pk, digest)
|
||||
} else {
|
||||
var b []byte
|
||||
b, err = priv.PrivateKey.(crypto.Signer).Sign(config.Random(), digest, sig.Hash)
|
||||
if err == nil {
|
||||
r, s, err = unwrapECDSASig(b)
|
||||
}
|
||||
}
|
||||
sk := priv.PrivateKey.(*ecdsa.PrivateKey)
|
||||
r, s, err := ecdsa.Sign(config.Random(), sk, digest)
|
||||
|
||||
if err == nil {
|
||||
sig.ECDSASigR = new(encoding.MPI).SetBig(r)
|
||||
sig.ECDSASigS = new(encoding.MPI).SetBig(s)
|
||||
}
|
||||
case PubKeyAlgoEdDSA:
|
||||
sigdata, err := priv.PrivateKey.(crypto.Signer).Sign(config.Random(), digest, crypto.Hash(0))
|
||||
sk := priv.PrivateKey.(*eddsa.PrivateKey)
|
||||
r, s, err := eddsa.Sign(sk, digest)
|
||||
if err == nil {
|
||||
sig.EdDSASigR = encoding.NewMPI(sigdata[:32])
|
||||
sig.EdDSASigS = encoding.NewMPI(sigdata[32:])
|
||||
sig.EdDSASigR = encoding.NewMPI(r)
|
||||
sig.EdDSASigS = encoding.NewMPI(s)
|
||||
}
|
||||
default:
|
||||
err = errors.UnsupportedError("public key algorithm: " + strconv.Itoa(int(sig.PubKeyAlgo)))
|
||||
@@ -677,19 +736,6 @@ func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err e
|
||||
return
|
||||
}
|
||||
|
||||
// unwrapECDSASig parses the two integer components of an ASN.1-encoded ECDSA
|
||||
// signature.
|
||||
func unwrapECDSASig(b []byte) (r, s *big.Int, err error) {
|
||||
var ecsdaSig struct {
|
||||
R, S *big.Int
|
||||
}
|
||||
_, err = asn1.Unmarshal(b, &ecsdaSig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ecsdaSig.R, ecsdaSig.S, nil
|
||||
}
|
||||
|
||||
// SignUserId computes a signature from priv, asserting that pub is a valid
|
||||
// key for the identity id. On success, the signature is stored in sig. Call
|
||||
// Serialize to write it out.
|
||||
@@ -858,12 +904,15 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp
|
||||
if sig.IssuerKeyId != nil && sig.Version == 4 {
|
||||
keyId := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(keyId, *sig.IssuerKeyId)
|
||||
subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, true, keyId})
|
||||
subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, false, keyId})
|
||||
}
|
||||
if sig.IssuerFingerprint != nil {
|
||||
contents := append([]uint8{uint8(issuer.Version)}, sig.IssuerFingerprint...)
|
||||
subpackets = append(subpackets, outputSubpacket{true, issuerFingerprintSubpacket, sig.Version == 5, contents})
|
||||
}
|
||||
if sig.SignerUserId != nil {
|
||||
subpackets = append(subpackets, outputSubpacket{true, signerUserIdSubpacket, false, []byte(*sig.SignerUserId)})
|
||||
}
|
||||
if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 {
|
||||
sigLifetime := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(sigLifetime, *sig.SigLifetimeSecs)
|
||||
@@ -898,23 +947,40 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp
|
||||
subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}})
|
||||
}
|
||||
|
||||
for _, notation := range sig.Notations {
|
||||
subpackets = append(
|
||||
subpackets,
|
||||
outputSubpacket{
|
||||
true,
|
||||
notationDataSubpacket,
|
||||
notation.IsCritical,
|
||||
notation.getData(),
|
||||
})
|
||||
}
|
||||
|
||||
// The following subpackets may only appear in self-signatures.
|
||||
|
||||
var features = byte(0x00)
|
||||
if sig.MDC {
|
||||
if sig.SEIPDv1 {
|
||||
features |= 0x01
|
||||
}
|
||||
if sig.AEAD {
|
||||
features |= 0x02
|
||||
}
|
||||
if sig.V5Keys {
|
||||
features |= 0x04
|
||||
if sig.SEIPDv2 {
|
||||
features |= 0x08
|
||||
}
|
||||
|
||||
if features != 0x00 {
|
||||
subpackets = append(subpackets, outputSubpacket{true, featuresSubpacket, false, []byte{features}})
|
||||
}
|
||||
|
||||
if sig.TrustLevel != 0 {
|
||||
subpackets = append(subpackets, outputSubpacket{true, trustSubpacket, true, []byte{byte(sig.TrustLevel), byte(sig.TrustAmount)}})
|
||||
}
|
||||
|
||||
if sig.TrustRegularExpression != nil {
|
||||
// RFC specifies the string should be null-terminated; add a null byte to the end
|
||||
subpackets = append(subpackets, outputSubpacket{true, regularExpressionSubpacket, true, []byte(*sig.TrustRegularExpression + "\000")})
|
||||
}
|
||||
|
||||
if sig.KeyLifetimeSecs != nil && *sig.KeyLifetimeSecs != 0 {
|
||||
keyLifetime := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(keyLifetime, *sig.KeyLifetimeSecs)
|
||||
@@ -941,8 +1007,13 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp
|
||||
subpackets = append(subpackets, outputSubpacket{true, policyUriSubpacket, false, []uint8(sig.PolicyURI)})
|
||||
}
|
||||
|
||||
if len(sig.PreferredAEAD) > 0 {
|
||||
subpackets = append(subpackets, outputSubpacket{true, prefAeadAlgosSubpacket, false, sig.PreferredAEAD})
|
||||
if len(sig.PreferredCipherSuites) > 0 {
|
||||
serialized := make([]byte, len(sig.PreferredCipherSuites)*2)
|
||||
for i, cipherSuite := range sig.PreferredCipherSuites {
|
||||
serialized[2*i] = cipherSuite[0]
|
||||
serialized[2*i+1] = cipherSuite[1]
|
||||
}
|
||||
subpackets = append(subpackets, outputSubpacket{true, prefCipherSuitesSubpacket, false, serialized})
|
||||
}
|
||||
|
||||
// Revocation reason appears only in revocation signatures and is serialized as per section 5.2.3.23.
|
||||
@@ -984,7 +1055,7 @@ func (sig *Signature) AddMetadataToHashSuffix() {
|
||||
n := sig.HashSuffix[len(sig.HashSuffix)-8:]
|
||||
l := uint64(
|
||||
uint64(n[0])<<56 | uint64(n[1])<<48 | uint64(n[2])<<40 | uint64(n[3])<<32 |
|
||||
uint64(n[4])<<24 | uint64(n[5])<<16 | uint64(n[6])<<8 | uint64(n[7]))
|
||||
uint64(n[4])<<24 | uint64(n[5])<<16 | uint64(n[6])<<8 | uint64(n[7]))
|
||||
|
||||
suffix := bytes.NewBuffer(nil)
|
||||
suffix.Write(sig.HashSuffix[:l])
|
||||
|
||||
+59
-50
@@ -14,8 +14,8 @@ import (
|
||||
"github.com/ProtonMail/go-crypto/openpgp/s2k"
|
||||
)
|
||||
|
||||
// This is the largest session key that we'll support. Since no 512-bit cipher
|
||||
// has even been seriously used, this is comfortably large.
|
||||
// This is the largest session key that we'll support. Since at most 256-bit cipher
|
||||
// is supported in OpenPGP, this is large enough to contain also the auth tag.
|
||||
const maxSessionKeySizeInBytes = 64
|
||||
|
||||
// SymmetricKeyEncrypted represents a passphrase protected session key. See RFC
|
||||
@@ -25,13 +25,16 @@ type SymmetricKeyEncrypted struct {
|
||||
CipherFunc CipherFunction
|
||||
Mode AEADMode
|
||||
s2k func(out, in []byte)
|
||||
aeadNonce []byte
|
||||
encryptedKey []byte
|
||||
iv []byte
|
||||
encryptedKey []byte // Contains also the authentication tag for AEAD
|
||||
}
|
||||
|
||||
// parse parses an SymmetricKeyEncrypted packet as specified in
|
||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#name-symmetric-key-encrypted-ses
|
||||
func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error {
|
||||
// RFC 4880, section 5.3.
|
||||
var buf [2]byte
|
||||
var buf [1]byte
|
||||
|
||||
// Version
|
||||
if _, err := readFull(r, buf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -39,17 +42,22 @@ func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error {
|
||||
if ske.Version != 4 && ske.Version != 5 {
|
||||
return errors.UnsupportedError("unknown SymmetricKeyEncrypted version")
|
||||
}
|
||||
ske.CipherFunc = CipherFunction(buf[1])
|
||||
if ske.CipherFunc.KeySize() == 0 {
|
||||
return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[1])))
|
||||
|
||||
// Cipher function
|
||||
if _, err := readFull(r, buf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
ske.CipherFunc = CipherFunction(buf[0])
|
||||
if !ske.CipherFunc.IsSupported() {
|
||||
return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[0])))
|
||||
}
|
||||
|
||||
if ske.Version == 5 {
|
||||
mode := make([]byte, 1)
|
||||
if _, err := r.Read(mode); err != nil {
|
||||
return errors.StructuralError("cannot read AEAD octect from packet")
|
||||
// AEAD mode
|
||||
if _, err := readFull(r, buf[:]); err != nil {
|
||||
return errors.StructuralError("cannot read AEAD octet from packet")
|
||||
}
|
||||
ske.Mode = AEADMode(mode[0])
|
||||
ske.Mode = AEADMode(buf[0])
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -61,13 +69,14 @@ func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error {
|
||||
}
|
||||
|
||||
if ske.Version == 5 {
|
||||
// AEAD nonce
|
||||
nonce := make([]byte, ske.Mode.NonceLength())
|
||||
_, err := readFull(r, nonce)
|
||||
if err != nil && err != io.ErrUnexpectedEOF {
|
||||
return err
|
||||
// AEAD IV
|
||||
iv := make([]byte, ske.Mode.IvLength())
|
||||
_, err := readFull(r, iv)
|
||||
if err != nil {
|
||||
return errors.StructuralError("cannot read AEAD IV")
|
||||
}
|
||||
ske.aeadNonce = nonce
|
||||
|
||||
ske.iv = iv
|
||||
}
|
||||
|
||||
encryptedKey := make([]byte, maxSessionKeySizeInBytes)
|
||||
@@ -128,11 +137,10 @@ func (ske *SymmetricKeyEncrypted) decryptV4(key []byte) ([]byte, CipherFunction,
|
||||
}
|
||||
|
||||
func (ske *SymmetricKeyEncrypted) decryptV5(key []byte) ([]byte, error) {
|
||||
blockCipher := CipherFunction(ske.CipherFunc).new(key)
|
||||
aead := ske.Mode.new(blockCipher)
|
||||
|
||||
adata := []byte{0xc3, byte(5), byte(ske.CipherFunc), byte(ske.Mode)}
|
||||
plaintextKey, err := aead.Open(nil, ske.aeadNonce, ske.encryptedKey, adata)
|
||||
aead := getEncryptedKeyAeadInstance(ske.CipherFunc, ske.Mode, key, adata)
|
||||
|
||||
plaintextKey, err := aead.Open(nil, ske.iv, ske.encryptedKey, adata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -142,17 +150,12 @@ func (ske *SymmetricKeyEncrypted) decryptV5(key []byte) ([]byte, error) {
|
||||
// SerializeSymmetricKeyEncrypted serializes a symmetric key packet to w.
|
||||
// The packet contains a random session key, encrypted by a key derived from
|
||||
// the given passphrase. The session key is returned and must be passed to
|
||||
// SerializeSymmetricallyEncrypted or SerializeAEADEncrypted, depending on
|
||||
// whether config.AEADConfig != nil.
|
||||
// SerializeSymmetricallyEncrypted.
|
||||
// If config is nil, sensible defaults will be used.
|
||||
func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Config) (key []byte, err error) {
|
||||
cipherFunc := config.Cipher()
|
||||
keySize := cipherFunc.KeySize()
|
||||
if keySize == 0 {
|
||||
return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(cipherFunc)))
|
||||
}
|
||||
|
||||
sessionKey := make([]byte, keySize)
|
||||
sessionKey := make([]byte, cipherFunc.KeySize())
|
||||
_, err = io.ReadFull(config.Random(), sessionKey)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -169,9 +172,8 @@ func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Conf
|
||||
|
||||
// SerializeSymmetricKeyEncryptedReuseKey serializes a symmetric key packet to w.
|
||||
// The packet contains the given session key, encrypted by a key derived from
|
||||
// the given passphrase. The session key must be passed to
|
||||
// SerializeSymmetricallyEncrypted or SerializeAEADEncrypted, depending on
|
||||
// whether config.AEADConfig != nil.
|
||||
// the given passphrase. The returned session key must be passed to
|
||||
// SerializeSymmetricallyEncrypted.
|
||||
// If config is nil, sensible defaults will be used.
|
||||
func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, passphrase []byte, config *Config) (err error) {
|
||||
var version int
|
||||
@@ -181,16 +183,17 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass
|
||||
version = 4
|
||||
}
|
||||
cipherFunc := config.Cipher()
|
||||
keySize := cipherFunc.KeySize()
|
||||
if keySize == 0 {
|
||||
return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(cipherFunc)))
|
||||
// cipherFunc must be AES
|
||||
if !cipherFunc.IsSupported() || cipherFunc < CipherAES128 || cipherFunc > CipherAES256 {
|
||||
return errors.UnsupportedError("unsupported cipher: " + strconv.Itoa(int(cipherFunc)))
|
||||
}
|
||||
|
||||
keySize := cipherFunc.KeySize()
|
||||
s2kBuf := new(bytes.Buffer)
|
||||
keyEncryptingKey := make([]byte, keySize)
|
||||
// s2k.Serialize salts and stretches the passphrase, and writes the
|
||||
// resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf.
|
||||
err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, &s2k.Config{Hash: config.Hash(), S2KCount: config.PasswordHashIterations()})
|
||||
err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, config.S2K())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -201,20 +204,20 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass
|
||||
case 4:
|
||||
packetLength = 2 /* header */ + len(s2kBytes) + 1 /* cipher type */ + keySize
|
||||
case 5:
|
||||
nonceLen := config.AEAD().Mode().NonceLength()
|
||||
ivLen := config.AEAD().Mode().IvLength()
|
||||
tagLen := config.AEAD().Mode().TagLength()
|
||||
packetLength = 3 + len(s2kBytes) + nonceLen + keySize + tagLen
|
||||
packetLength = 3 + len(s2kBytes) + ivLen + keySize + tagLen
|
||||
}
|
||||
err = serializeHeader(w, packetTypeSymmetricKeyEncrypted, packetLength)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, 2)
|
||||
// Symmetric Key Encrypted Version
|
||||
buf[0] = byte(version)
|
||||
buf := []byte{byte(version)}
|
||||
|
||||
// Cipher function
|
||||
buf[1] = byte(cipherFunc)
|
||||
buf = append(buf, byte(cipherFunc))
|
||||
|
||||
if version == 5 {
|
||||
// AEAD mode
|
||||
@@ -241,19 +244,20 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass
|
||||
return
|
||||
}
|
||||
case 5:
|
||||
blockCipher := cipherFunc.new(keyEncryptingKey)
|
||||
mode := config.AEAD().Mode()
|
||||
aead := mode.new(blockCipher)
|
||||
// Sample nonce using random reader
|
||||
nonce := make([]byte, config.AEAD().Mode().NonceLength())
|
||||
_, err = io.ReadFull(config.Random(), nonce)
|
||||
adata := []byte{0xc3, byte(5), byte(cipherFunc), byte(mode)}
|
||||
aead := getEncryptedKeyAeadInstance(cipherFunc, mode, keyEncryptingKey, adata)
|
||||
|
||||
// Sample iv using random reader
|
||||
iv := make([]byte, config.AEAD().Mode().IvLength())
|
||||
_, err = io.ReadFull(config.Random(), iv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Seal and write (encryptedData includes auth. tag)
|
||||
adata := []byte{0xc3, byte(5), byte(cipherFunc), byte(mode)}
|
||||
encryptedData := aead.Seal(nil, nonce, sessionKey, adata)
|
||||
_, err = w.Write(nonce)
|
||||
|
||||
encryptedData := aead.Seal(nil, iv, sessionKey, adata)
|
||||
_, err = w.Write(iv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -265,3 +269,8 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getEncryptedKeyAeadInstance(c CipherFunction, mode AEADMode, inputKey, associatedData []byte) (aead cipher.AEAD) {
|
||||
blockCipher := c.new(inputKey)
|
||||
return mode.new(blockCipher)
|
||||
}
|
||||
|
||||
+37
-237
@@ -5,36 +5,54 @@
|
||||
package packet
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/sha1"
|
||||
"crypto/subtle"
|
||||
"hash"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
)
|
||||
|
||||
const aeadSaltSize = 32
|
||||
|
||||
// SymmetricallyEncrypted represents a symmetrically encrypted byte string. The
|
||||
// encrypted Contents will consist of more OpenPGP packets. See RFC 4880,
|
||||
// sections 5.7 and 5.13.
|
||||
type SymmetricallyEncrypted struct {
|
||||
MDC bool // true iff this is a type 18 packet and thus has an embedded MAC.
|
||||
Contents io.Reader
|
||||
prefix []byte
|
||||
Version int
|
||||
Contents io.Reader // contains tag for version 2
|
||||
IntegrityProtected bool // If true it is type 18 (with MDC or AEAD). False is packet type 9
|
||||
|
||||
// Specific to version 1
|
||||
prefix []byte
|
||||
|
||||
// Specific to version 2
|
||||
Cipher CipherFunction
|
||||
Mode AEADMode
|
||||
ChunkSizeByte byte
|
||||
Salt [aeadSaltSize]byte
|
||||
}
|
||||
|
||||
const symmetricallyEncryptedVersion = 1
|
||||
const (
|
||||
symmetricallyEncryptedVersionMdc = 1
|
||||
symmetricallyEncryptedVersionAead = 2
|
||||
)
|
||||
|
||||
func (se *SymmetricallyEncrypted) parse(r io.Reader) error {
|
||||
if se.MDC {
|
||||
if se.IntegrityProtected {
|
||||
// See RFC 4880, section 5.13.
|
||||
var buf [1]byte
|
||||
_, err := readFull(r, buf[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if buf[0] != symmetricallyEncryptedVersion {
|
||||
|
||||
switch buf[0] {
|
||||
case symmetricallyEncryptedVersionMdc:
|
||||
se.Version = symmetricallyEncryptedVersionMdc
|
||||
case symmetricallyEncryptedVersionAead:
|
||||
se.Version = symmetricallyEncryptedVersionAead
|
||||
if err := se.parseAead(r); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.UnsupportedError("unknown SymmetricallyEncrypted version")
|
||||
}
|
||||
}
|
||||
@@ -46,245 +64,27 @@ func (se *SymmetricallyEncrypted) parse(r io.Reader) error {
|
||||
// packet can be read. An incorrect key will only be detected after trying
|
||||
// to decrypt the entire data.
|
||||
func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) {
|
||||
keySize := c.KeySize()
|
||||
if keySize == 0 {
|
||||
return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(c)))
|
||||
}
|
||||
if len(key) != keySize {
|
||||
return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length")
|
||||
if se.Version == symmetricallyEncryptedVersionAead {
|
||||
return se.decryptAead(key)
|
||||
}
|
||||
|
||||
if se.prefix == nil {
|
||||
se.prefix = make([]byte, c.blockSize()+2)
|
||||
_, err := readFull(se.Contents, se.prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if len(se.prefix) != c.blockSize()+2 {
|
||||
return nil, errors.InvalidArgumentError("can't try ciphers with different block lengths")
|
||||
}
|
||||
|
||||
ocfbResync := OCFBResync
|
||||
if se.MDC {
|
||||
// MDC packets use a different form of OCFB mode.
|
||||
ocfbResync = OCFBNoResync
|
||||
}
|
||||
|
||||
s := NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync)
|
||||
|
||||
plaintext := cipher.StreamReader{S: s, R: se.Contents}
|
||||
|
||||
if se.MDC {
|
||||
// MDC packets have an embedded hash that we need to check.
|
||||
h := sha1.New()
|
||||
h.Write(se.prefix)
|
||||
return &seMDCReader{in: plaintext, h: h}, nil
|
||||
}
|
||||
|
||||
// Otherwise, we just need to wrap plaintext so that it's a valid ReadCloser.
|
||||
return seReader{plaintext}, nil
|
||||
}
|
||||
|
||||
// seReader wraps an io.Reader with a no-op Close method.
|
||||
type seReader struct {
|
||||
in io.Reader
|
||||
}
|
||||
|
||||
func (ser seReader) Read(buf []byte) (int, error) {
|
||||
return ser.in.Read(buf)
|
||||
}
|
||||
|
||||
func (ser seReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
const mdcTrailerSize = 1 /* tag byte */ + 1 /* length byte */ + sha1.Size
|
||||
|
||||
// An seMDCReader wraps an io.Reader, maintains a running hash and keeps hold
|
||||
// of the most recent 22 bytes (mdcTrailerSize). Upon EOF, those bytes form an
|
||||
// MDC packet containing a hash of the previous Contents which is checked
|
||||
// against the running hash. See RFC 4880, section 5.13.
|
||||
type seMDCReader struct {
|
||||
in io.Reader
|
||||
h hash.Hash
|
||||
trailer [mdcTrailerSize]byte
|
||||
scratch [mdcTrailerSize]byte
|
||||
trailerUsed int
|
||||
error bool
|
||||
eof bool
|
||||
}
|
||||
|
||||
func (ser *seMDCReader) Read(buf []byte) (n int, err error) {
|
||||
if ser.error {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
if ser.eof {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
|
||||
// If we haven't yet filled the trailer buffer then we must do that
|
||||
// first.
|
||||
for ser.trailerUsed < mdcTrailerSize {
|
||||
n, err = ser.in.Read(ser.trailer[ser.trailerUsed:])
|
||||
ser.trailerUsed += n
|
||||
if err == io.EOF {
|
||||
if ser.trailerUsed != mdcTrailerSize {
|
||||
n = 0
|
||||
err = io.ErrUnexpectedEOF
|
||||
ser.error = true
|
||||
return
|
||||
}
|
||||
ser.eof = true
|
||||
n = 0
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
n = 0
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If it's a short read then we read into a temporary buffer and shift
|
||||
// the data into the caller's buffer.
|
||||
if len(buf) <= mdcTrailerSize {
|
||||
n, err = readFull(ser.in, ser.scratch[:len(buf)])
|
||||
copy(buf, ser.trailer[:n])
|
||||
ser.h.Write(buf[:n])
|
||||
copy(ser.trailer[:], ser.trailer[n:])
|
||||
copy(ser.trailer[mdcTrailerSize-n:], ser.scratch[:])
|
||||
if n < len(buf) {
|
||||
ser.eof = true
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
n, err = ser.in.Read(buf[mdcTrailerSize:])
|
||||
copy(buf, ser.trailer[:])
|
||||
ser.h.Write(buf[:n])
|
||||
copy(ser.trailer[:], buf[n:])
|
||||
|
||||
if err == io.EOF {
|
||||
ser.eof = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// This is a new-format packet tag byte for a type 19 (MDC) packet.
|
||||
const mdcPacketTagByte = byte(0x80) | 0x40 | 19
|
||||
|
||||
func (ser *seMDCReader) Close() error {
|
||||
if ser.error {
|
||||
return errors.ErrMDCMissing
|
||||
}
|
||||
|
||||
for !ser.eof {
|
||||
// We haven't seen EOF so we need to read to the end
|
||||
var buf [1024]byte
|
||||
_, err := ser.Read(buf[:])
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return errors.ErrMDCMissing
|
||||
}
|
||||
}
|
||||
|
||||
ser.h.Write(ser.trailer[:2])
|
||||
|
||||
final := ser.h.Sum(nil)
|
||||
if subtle.ConstantTimeCompare(final, ser.trailer[2:]) != 1 {
|
||||
return errors.ErrMDCHashMismatch
|
||||
}
|
||||
// The hash already includes the MDC header, but we still check its value
|
||||
// to confirm encryption correctness
|
||||
if ser.trailer[0] != mdcPacketTagByte || ser.trailer[1] != sha1.Size {
|
||||
return errors.ErrMDCMissing
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// An seMDCWriter writes through to an io.WriteCloser while maintains a running
|
||||
// hash of the data written. On close, it emits an MDC packet containing the
|
||||
// running hash.
|
||||
type seMDCWriter struct {
|
||||
w io.WriteCloser
|
||||
h hash.Hash
|
||||
}
|
||||
|
||||
func (w *seMDCWriter) Write(buf []byte) (n int, err error) {
|
||||
w.h.Write(buf)
|
||||
return w.w.Write(buf)
|
||||
}
|
||||
|
||||
func (w *seMDCWriter) Close() (err error) {
|
||||
var buf [mdcTrailerSize]byte
|
||||
|
||||
buf[0] = mdcPacketTagByte
|
||||
buf[1] = sha1.Size
|
||||
w.h.Write(buf[:2])
|
||||
digest := w.h.Sum(nil)
|
||||
copy(buf[2:], digest)
|
||||
|
||||
_, err = w.w.Write(buf[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return w.w.Close()
|
||||
}
|
||||
|
||||
// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
|
||||
type noOpCloser struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (c noOpCloser) Write(data []byte) (n int, err error) {
|
||||
return c.w.Write(data)
|
||||
}
|
||||
|
||||
func (c noOpCloser) Close() error {
|
||||
return nil
|
||||
return se.decryptMdc(c, key)
|
||||
}
|
||||
|
||||
// SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet
|
||||
// to w and returns a WriteCloser to which the to-be-encrypted packets can be
|
||||
// written.
|
||||
// If config is nil, sensible defaults will be used.
|
||||
func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, key []byte, config *Config) (Contents io.WriteCloser, err error) {
|
||||
if c.KeySize() != len(key) {
|
||||
return nil, errors.InvalidArgumentError("SymmetricallyEncrypted.Serialize: bad key length")
|
||||
}
|
||||
func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, aeadSupported bool, cipherSuite CipherSuite, key []byte, config *Config) (Contents io.WriteCloser, err error) {
|
||||
writeCloser := noOpCloser{w}
|
||||
ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedMDC)
|
||||
ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedIntegrityProtected)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = ciphertext.Write([]byte{symmetricallyEncryptedVersion})
|
||||
if err != nil {
|
||||
return
|
||||
if aeadSupported {
|
||||
return serializeSymmetricallyEncryptedAead(ciphertext, cipherSuite, config.AEADConfig.ChunkSizeByte(), config.Random(), key)
|
||||
}
|
||||
|
||||
block := c.new(key)
|
||||
blockSize := block.BlockSize()
|
||||
iv := make([]byte, blockSize)
|
||||
_, err = config.Random().Read(iv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s, prefix := NewOCFBEncrypter(block, iv, OCFBNoResync)
|
||||
_, err = ciphertext.Write(prefix)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
plaintext := cipher.StreamWriter{S: s, W: ciphertext}
|
||||
|
||||
h := sha1.New()
|
||||
h.Write(iv)
|
||||
h.Write(iv[blockSize-2:])
|
||||
Contents = &seMDCWriter{w: plaintext, h: h}
|
||||
return
|
||||
return serializeSymmetricallyEncryptedMdc(ciphertext, c, key, config)
|
||||
}
|
||||
|
||||
Generated
Vendored
+156
@@ -0,0 +1,156 @@
|
||||
// Copyright 2023 Proton AG. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package packet
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
// parseAead parses a V2 SEIPD packet (AEAD) as specified in
|
||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
|
||||
func (se *SymmetricallyEncrypted) parseAead(r io.Reader) error {
|
||||
headerData := make([]byte, 3)
|
||||
if n, err := io.ReadFull(r, headerData); n < 3 {
|
||||
return errors.StructuralError("could not read aead header: " + err.Error())
|
||||
}
|
||||
|
||||
// Cipher
|
||||
se.Cipher = CipherFunction(headerData[0])
|
||||
// cipherFunc must have block size 16 to use AEAD
|
||||
if se.Cipher.blockSize() != 16 {
|
||||
return errors.UnsupportedError("invalid aead cipher: " + string(se.Cipher))
|
||||
}
|
||||
|
||||
// Mode
|
||||
se.Mode = AEADMode(headerData[1])
|
||||
if se.Mode.TagLength() == 0 {
|
||||
return errors.UnsupportedError("unknown aead mode: " + string(se.Mode))
|
||||
}
|
||||
|
||||
// Chunk size
|
||||
se.ChunkSizeByte = headerData[2]
|
||||
if se.ChunkSizeByte > 16 {
|
||||
return errors.UnsupportedError("invalid aead chunk size byte: " + string(se.ChunkSizeByte))
|
||||
}
|
||||
|
||||
// Salt
|
||||
if n, err := io.ReadFull(r, se.Salt[:]); n < aeadSaltSize {
|
||||
return errors.StructuralError("could not read aead salt: " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// associatedData for chunks: tag, version, cipher, mode, chunk size byte
|
||||
func (se *SymmetricallyEncrypted) associatedData() []byte {
|
||||
return []byte{
|
||||
0xD2,
|
||||
symmetricallyEncryptedVersionAead,
|
||||
byte(se.Cipher),
|
||||
byte(se.Mode),
|
||||
se.ChunkSizeByte,
|
||||
}
|
||||
}
|
||||
|
||||
// decryptAead decrypts a V2 SEIPD packet (AEAD) as specified in
|
||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
|
||||
func (se *SymmetricallyEncrypted) decryptAead(inputKey []byte) (io.ReadCloser, error) {
|
||||
aead, nonce := getSymmetricallyEncryptedAeadInstance(se.Cipher, se.Mode, inputKey, se.Salt[:], se.associatedData())
|
||||
|
||||
// Carry the first tagLen bytes
|
||||
tagLen := se.Mode.TagLength()
|
||||
peekedBytes := make([]byte, tagLen)
|
||||
n, err := io.ReadFull(se.Contents, peekedBytes)
|
||||
if n < tagLen || (err != nil && err != io.EOF) {
|
||||
return nil, errors.StructuralError("not enough data to decrypt:" + err.Error())
|
||||
}
|
||||
|
||||
return &aeadDecrypter{
|
||||
aeadCrypter: aeadCrypter{
|
||||
aead: aead,
|
||||
chunkSize: decodeAEADChunkSize(se.ChunkSizeByte),
|
||||
initialNonce: nonce,
|
||||
associatedData: se.associatedData(),
|
||||
chunkIndex: make([]byte, 8),
|
||||
packetTag: packetTypeSymmetricallyEncryptedIntegrityProtected,
|
||||
},
|
||||
reader: se.Contents,
|
||||
peekedBytes: peekedBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// serializeSymmetricallyEncryptedAead encrypts to a writer a V2 SEIPD packet (AEAD) as specified in
|
||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
|
||||
func serializeSymmetricallyEncryptedAead(ciphertext io.WriteCloser, cipherSuite CipherSuite, chunkSizeByte byte, rand io.Reader, inputKey []byte) (Contents io.WriteCloser, err error) {
|
||||
// cipherFunc must have block size 16 to use AEAD
|
||||
if cipherSuite.Cipher.blockSize() != 16 {
|
||||
return nil, errors.InvalidArgumentError("invalid aead cipher function")
|
||||
}
|
||||
|
||||
if cipherSuite.Cipher.KeySize() != len(inputKey) {
|
||||
return nil, errors.InvalidArgumentError("error in aead serialization: bad key length")
|
||||
}
|
||||
|
||||
// Data for en/decryption: tag, version, cipher, aead mode, chunk size
|
||||
prefix := []byte{
|
||||
0xD2,
|
||||
symmetricallyEncryptedVersionAead,
|
||||
byte(cipherSuite.Cipher),
|
||||
byte(cipherSuite.Mode),
|
||||
chunkSizeByte,
|
||||
}
|
||||
|
||||
// Write header (that correspond to prefix except first byte)
|
||||
n, err := ciphertext.Write(prefix[1:])
|
||||
if err != nil || n < 4 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Random salt
|
||||
salt := make([]byte, aeadSaltSize)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := ciphertext.Write(salt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aead, nonce := getSymmetricallyEncryptedAeadInstance(cipherSuite.Cipher, cipherSuite.Mode, inputKey, salt, prefix)
|
||||
|
||||
return &aeadEncrypter{
|
||||
aeadCrypter: aeadCrypter{
|
||||
aead: aead,
|
||||
chunkSize: decodeAEADChunkSize(chunkSizeByte),
|
||||
associatedData: prefix,
|
||||
chunkIndex: make([]byte, 8),
|
||||
initialNonce: nonce,
|
||||
packetTag: packetTypeSymmetricallyEncryptedIntegrityProtected,
|
||||
},
|
||||
writer: ciphertext,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getSymmetricallyEncryptedAeadInstance(c CipherFunction, mode AEADMode, inputKey, salt, associatedData []byte) (aead cipher.AEAD, nonce []byte) {
|
||||
hkdfReader := hkdf.New(sha256.New, inputKey, salt, associatedData)
|
||||
|
||||
encryptionKey := make([]byte, c.KeySize())
|
||||
_, _ = readFull(hkdfReader, encryptionKey)
|
||||
|
||||
// Last 64 bits of nonce are the counter
|
||||
nonce = make([]byte, mode.IvLength()-8)
|
||||
|
||||
_, _ = readFull(hkdfReader, nonce)
|
||||
|
||||
blockCipher := c.new(encryptionKey)
|
||||
aead = mode.new(blockCipher)
|
||||
|
||||
return
|
||||
}
|
||||
Generated
Vendored
+256
@@ -0,0 +1,256 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package packet
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/sha1"
|
||||
"crypto/subtle"
|
||||
"hash"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
)
|
||||
|
||||
// seMdcReader wraps an io.Reader with a no-op Close method.
|
||||
type seMdcReader struct {
|
||||
in io.Reader
|
||||
}
|
||||
|
||||
func (ser seMdcReader) Read(buf []byte) (int, error) {
|
||||
return ser.in.Read(buf)
|
||||
}
|
||||
|
||||
func (ser seMdcReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (se *SymmetricallyEncrypted) decryptMdc(c CipherFunction, key []byte) (io.ReadCloser, error) {
|
||||
if !c.IsSupported() {
|
||||
return nil, errors.UnsupportedError("unsupported cipher: " + strconv.Itoa(int(c)))
|
||||
}
|
||||
|
||||
if len(key) != c.KeySize() {
|
||||
return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length")
|
||||
}
|
||||
|
||||
if se.prefix == nil {
|
||||
se.prefix = make([]byte, c.blockSize()+2)
|
||||
_, err := readFull(se.Contents, se.prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if len(se.prefix) != c.blockSize()+2 {
|
||||
return nil, errors.InvalidArgumentError("can't try ciphers with different block lengths")
|
||||
}
|
||||
|
||||
ocfbResync := OCFBResync
|
||||
if se.IntegrityProtected {
|
||||
// MDC packets use a different form of OCFB mode.
|
||||
ocfbResync = OCFBNoResync
|
||||
}
|
||||
|
||||
s := NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync)
|
||||
|
||||
plaintext := cipher.StreamReader{S: s, R: se.Contents}
|
||||
|
||||
if se.IntegrityProtected {
|
||||
// IntegrityProtected packets have an embedded hash that we need to check.
|
||||
h := sha1.New()
|
||||
h.Write(se.prefix)
|
||||
return &seMDCReader{in: plaintext, h: h}, nil
|
||||
}
|
||||
|
||||
// Otherwise, we just need to wrap plaintext so that it's a valid ReadCloser.
|
||||
return seMdcReader{plaintext}, nil
|
||||
}
|
||||
|
||||
const mdcTrailerSize = 1 /* tag byte */ + 1 /* length byte */ + sha1.Size
|
||||
|
||||
// An seMDCReader wraps an io.Reader, maintains a running hash and keeps hold
|
||||
// of the most recent 22 bytes (mdcTrailerSize). Upon EOF, those bytes form an
|
||||
// MDC packet containing a hash of the previous Contents which is checked
|
||||
// against the running hash. See RFC 4880, section 5.13.
|
||||
type seMDCReader struct {
|
||||
in io.Reader
|
||||
h hash.Hash
|
||||
trailer [mdcTrailerSize]byte
|
||||
scratch [mdcTrailerSize]byte
|
||||
trailerUsed int
|
||||
error bool
|
||||
eof bool
|
||||
}
|
||||
|
||||
func (ser *seMDCReader) Read(buf []byte) (n int, err error) {
|
||||
if ser.error {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
if ser.eof {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
|
||||
// If we haven't yet filled the trailer buffer then we must do that
|
||||
// first.
|
||||
for ser.trailerUsed < mdcTrailerSize {
|
||||
n, err = ser.in.Read(ser.trailer[ser.trailerUsed:])
|
||||
ser.trailerUsed += n
|
||||
if err == io.EOF {
|
||||
if ser.trailerUsed != mdcTrailerSize {
|
||||
n = 0
|
||||
err = io.ErrUnexpectedEOF
|
||||
ser.error = true
|
||||
return
|
||||
}
|
||||
ser.eof = true
|
||||
n = 0
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
n = 0
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If it's a short read then we read into a temporary buffer and shift
|
||||
// the data into the caller's buffer.
|
||||
if len(buf) <= mdcTrailerSize {
|
||||
n, err = readFull(ser.in, ser.scratch[:len(buf)])
|
||||
copy(buf, ser.trailer[:n])
|
||||
ser.h.Write(buf[:n])
|
||||
copy(ser.trailer[:], ser.trailer[n:])
|
||||
copy(ser.trailer[mdcTrailerSize-n:], ser.scratch[:])
|
||||
if n < len(buf) {
|
||||
ser.eof = true
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
n, err = ser.in.Read(buf[mdcTrailerSize:])
|
||||
copy(buf, ser.trailer[:])
|
||||
ser.h.Write(buf[:n])
|
||||
copy(ser.trailer[:], buf[n:])
|
||||
|
||||
if err == io.EOF {
|
||||
ser.eof = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// This is a new-format packet tag byte for a type 19 (Integrity Protected) packet.
|
||||
const mdcPacketTagByte = byte(0x80) | 0x40 | 19
|
||||
|
||||
func (ser *seMDCReader) Close() error {
|
||||
if ser.error {
|
||||
return errors.ErrMDCMissing
|
||||
}
|
||||
|
||||
for !ser.eof {
|
||||
// We haven't seen EOF so we need to read to the end
|
||||
var buf [1024]byte
|
||||
_, err := ser.Read(buf[:])
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return errors.ErrMDCMissing
|
||||
}
|
||||
}
|
||||
|
||||
ser.h.Write(ser.trailer[:2])
|
||||
|
||||
final := ser.h.Sum(nil)
|
||||
if subtle.ConstantTimeCompare(final, ser.trailer[2:]) != 1 {
|
||||
return errors.ErrMDCHashMismatch
|
||||
}
|
||||
// The hash already includes the MDC header, but we still check its value
|
||||
// to confirm encryption correctness
|
||||
if ser.trailer[0] != mdcPacketTagByte || ser.trailer[1] != sha1.Size {
|
||||
return errors.ErrMDCMissing
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// An seMDCWriter writes through to an io.WriteCloser while maintains a running
|
||||
// hash of the data written. On close, it emits an MDC packet containing the
|
||||
// running hash.
|
||||
type seMDCWriter struct {
|
||||
w io.WriteCloser
|
||||
h hash.Hash
|
||||
}
|
||||
|
||||
func (w *seMDCWriter) Write(buf []byte) (n int, err error) {
|
||||
w.h.Write(buf)
|
||||
return w.w.Write(buf)
|
||||
}
|
||||
|
||||
func (w *seMDCWriter) Close() (err error) {
|
||||
var buf [mdcTrailerSize]byte
|
||||
|
||||
buf[0] = mdcPacketTagByte
|
||||
buf[1] = sha1.Size
|
||||
w.h.Write(buf[:2])
|
||||
digest := w.h.Sum(nil)
|
||||
copy(buf[2:], digest)
|
||||
|
||||
_, err = w.w.Write(buf[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return w.w.Close()
|
||||
}
|
||||
|
||||
// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
|
||||
type noOpCloser struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (c noOpCloser) Write(data []byte) (n int, err error) {
|
||||
return c.w.Write(data)
|
||||
}
|
||||
|
||||
func (c noOpCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func serializeSymmetricallyEncryptedMdc(ciphertext io.WriteCloser, c CipherFunction, key []byte, config *Config) (Contents io.WriteCloser, err error) {
|
||||
// Disallow old cipher suites
|
||||
if !c.IsSupported() || c < CipherAES128 {
|
||||
return nil, errors.InvalidArgumentError("invalid mdc cipher function")
|
||||
}
|
||||
|
||||
if c.KeySize() != len(key) {
|
||||
return nil, errors.InvalidArgumentError("error in mdc serialization: bad key length")
|
||||
}
|
||||
|
||||
_, err = ciphertext.Write([]byte{symmetricallyEncryptedVersionMdc})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
block := c.new(key)
|
||||
blockSize := block.BlockSize()
|
||||
iv := make([]byte, blockSize)
|
||||
_, err = config.Random().Read(iv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s, prefix := NewOCFBEncrypter(block, iv, OCFBNoResync)
|
||||
_, err = ciphertext.Write(prefix)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
plaintext := cipher.StreamWriter{S: s, W: ciphertext}
|
||||
|
||||
h := sha1.New()
|
||||
h.Write(iv)
|
||||
h.Write(iv[blockSize-2:])
|
||||
Contents = &seMDCWriter{w: plaintext, h: h}
|
||||
return
|
||||
}
|
||||
+9
-2
@@ -42,9 +42,16 @@ func NewUserAttributePhoto(photos ...image.Image) (uat *UserAttribute, err error
|
||||
if err = jpeg.Encode(&buf, photo, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lengthBuf := make([]byte, 5)
|
||||
n := serializeSubpacketLength(lengthBuf, len(buf.Bytes())+1)
|
||||
lengthBuf = lengthBuf[:n]
|
||||
|
||||
uat.Contents = append(uat.Contents, &OpaqueSubpacket{
|
||||
SubType: UserAttrImageSubpacket,
|
||||
Contents: buf.Bytes()})
|
||||
SubType: UserAttrImageSubpacket,
|
||||
EncodedLength: lengthBuf,
|
||||
Contents: buf.Bytes(),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user