Add sender authenticated message integrity; fixup KDF

- use HKDF for producing keys, nonces
- add running hmac of plaintext; sender-sign the hmac as trailer
- use header checksum as "salt" for data encryption keys, nonces
- generate explicit nonce for wrapping root keys for each recipient
  (previous impl had brittleness)
This commit is contained in:
Sudhi Herle 2022-11-13 11:53:00 -08:00
parent a428db8feb
commit f343d45a8e
6 changed files with 495 additions and 249 deletions

View file

@ -34,7 +34,7 @@ type Header struct {
ChunkSize uint32 `protobuf:"varint,1,opt,name=chunk_size,json=chunkSize,proto3" json:"chunk_size,omitempty"` ChunkSize uint32 `protobuf:"varint,1,opt,name=chunk_size,json=chunkSize,proto3" json:"chunk_size,omitempty"`
Salt []byte `protobuf:"bytes,2,opt,name=salt,proto3" json:"salt,omitempty"` Salt []byte `protobuf:"bytes,2,opt,name=salt,proto3" json:"salt,omitempty"`
Pk []byte `protobuf:"bytes,3,opt,name=pk,proto3" json:"pk,omitempty"` Pk []byte `protobuf:"bytes,3,opt,name=pk,proto3" json:"pk,omitempty"`
SenderSign []byte `protobuf:"bytes,4,opt,name=sender_sign,json=senderSign,proto3" json:"sender_sign,omitempty"` Sender []byte `protobuf:"bytes,4,opt,name=sender,proto3" json:"sender,omitempty"`
Keys []*WrappedKey `protobuf:"bytes,5,rep,name=keys,proto3" json:"keys,omitempty"` Keys []*WrappedKey `protobuf:"bytes,5,rep,name=keys,proto3" json:"keys,omitempty"`
} }
@ -91,9 +91,9 @@ func (m *Header) GetPk() []byte {
return nil return nil
} }
func (m *Header) GetSenderSign() []byte { func (m *Header) GetSender() []byte {
if m != nil { if m != nil {
return m.SenderSign return m.Sender
} }
return nil return nil
} }
@ -110,6 +110,7 @@ func (m *Header) GetKeys() []*WrappedKey {
// key. WrappedKey describes such a wrapped key. // key. WrappedKey describes such a wrapped key.
type WrappedKey struct { type WrappedKey struct {
DKey []byte `protobuf:"bytes,1,opt,name=d_key,json=dKey,proto3" json:"d_key,omitempty"` DKey []byte `protobuf:"bytes,1,opt,name=d_key,json=dKey,proto3" json:"d_key,omitempty"`
Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
} }
func (m *WrappedKey) Reset() { *m = WrappedKey{} } func (m *WrappedKey) Reset() { *m = WrappedKey{} }
@ -151,6 +152,13 @@ func (m *WrappedKey) GetDKey() []byte {
return nil return nil
} }
func (m *WrappedKey) GetNonce() []byte {
if m != nil {
return m.Nonce
}
return nil
}
func init() { func init() {
proto.RegisterType((*Header)(nil), "pb.header") proto.RegisterType((*Header)(nil), "pb.header")
proto.RegisterType((*WrappedKey)(nil), "pb.wrapped_key") proto.RegisterType((*WrappedKey)(nil), "pb.wrapped_key")
@ -159,24 +167,24 @@ func init() {
func init() { proto.RegisterFile("internal/pb/hdr.proto", fileDescriptor_c715362029a696e2) } func init() { proto.RegisterFile("internal/pb/hdr.proto", fileDescriptor_c715362029a696e2) }
var fileDescriptor_c715362029a696e2 = []byte{ var fileDescriptor_c715362029a696e2 = []byte{
// 257 bytes of a gzipped FileDescriptorProto // 262 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0xcf, 0x31, 0x4e, 0xeb, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0xd0, 0x3f, 0x4e, 0xf3, 0x40,
0x10, 0xc6, 0xf1, 0x1d, 0xc7, 0x89, 0xf4, 0x26, 0x79, 0x20, 0x2d, 0x42, 0x72, 0xc3, 0x60, 0x99, 0x10, 0x05, 0xf0, 0x1d, 0xc7, 0x89, 0xf4, 0x4d, 0xf2, 0x81, 0xb4, 0xfc, 0x91, 0x1b, 0x46, 0x56,
0xc6, 0x95, 0x23, 0x01, 0x27, 0xa0, 0xa5, 0x73, 0x0e, 0x60, 0xd9, 0x78, 0x14, 0x5b, 0x8e, 0x36, 0x68, 0x5c, 0x39, 0x12, 0x50, 0x50, 0xd3, 0xd2, 0x99, 0x03, 0x44, 0x76, 0x3c, 0x92, 0x2d, 0x47,
0xab, 0xb5, 0x11, 0x72, 0x2a, 0x8e, 0x00, 0xb7, 0xe0, 0x28, 0x94, 0x2e, 0x53, 0xe2, 0x75, 0x43, 0xeb, 0xd5, 0xda, 0x08, 0x39, 0x15, 0x25, 0x25, 0xc7, 0xe0, 0x28, 0x94, 0x2e, 0x53, 0xe2, 0x75,
0x99, 0x23, 0x20, 0x2d, 0x0d, 0xdd, 0xa7, 0xdf, 0xbf, 0x99, 0xc1, 0xcb, 0x5a, 0x75, 0x6c, 0x54, 0x43, 0x99, 0x23, 0x20, 0x2d, 0x29, 0xe8, 0xde, 0xfb, 0x4d, 0xf3, 0x34, 0x78, 0x51, 0xaa, 0x96,
0xbe, 0x5b, 0xeb, 0x62, 0x5d, 0x95, 0x26, 0xd1, 0x66, 0xdf, 0xed, 0xa5, 0xa7, 0x8b, 0xe8, 0x1d, 0x8d, 0x4a, 0xb7, 0x2b, 0x9d, 0xad, 0x8a, 0xdc, 0xc4, 0xda, 0xd4, 0x6d, 0x2d, 0x3d, 0x9d, 0x2d,
0x70, 0x51, 0x71, 0x5e, 0xb2, 0x91, 0x57, 0x88, 0x4f, 0xd5, 0xb3, 0x6a, 0xb2, 0xb6, 0x3e, 0x70, 0xdf, 0x00, 0x67, 0x05, 0xa7, 0x39, 0x1b, 0x79, 0x85, 0xb8, 0x29, 0x9e, 0x55, 0xb5, 0x6e, 0xca,
0x00, 0x21, 0xc4, 0xff, 0xd3, 0x7f, 0x4e, 0x36, 0xf5, 0x81, 0xa5, 0x44, 0xbf, 0xcd, 0x77, 0x5d, 0x1d, 0x07, 0x10, 0x42, 0xf4, 0x3f, 0xf9, 0xe7, 0xe4, 0xa9, 0xdc, 0xb1, 0x94, 0xe8, 0x37, 0xe9,
0xe0, 0x85, 0x10, 0xaf, 0x52, 0xb7, 0xe5, 0x19, 0x7a, 0xba, 0x09, 0x66, 0x4e, 0x3c, 0xdd, 0xc8, 0xb6, 0x0d, 0xbc, 0x10, 0xa2, 0x45, 0xe2, 0xb2, 0x3c, 0x41, 0x4f, 0x57, 0xc1, 0xc4, 0x89, 0xa7,
0x6b, 0x5c, 0xb6, 0xac, 0x4a, 0x36, 0x59, 0x5b, 0x6f, 0x55, 0xe0, 0xbb, 0x80, 0xbf, 0xb4, 0xa9, 0x2b, 0x79, 0x89, 0xb3, 0x86, 0x55, 0xce, 0x26, 0xf0, 0x9d, 0x1d, 0x9b, 0xbc, 0x46, 0xbf, 0xe2,
0xb7, 0x4a, 0xde, 0xa0, 0xdf, 0x70, 0xdf, 0x06, 0xf3, 0x70, 0x16, 0x2f, 0x6f, 0xcf, 0x13, 0x5d, 0xae, 0x09, 0xa6, 0xe1, 0x24, 0x9a, 0xdf, 0x9c, 0xc6, 0x3a, 0x8b, 0x5f, 0x4c, 0xaa, 0x35, 0xe7,
0x24, 0x2f, 0x26, 0xd7, 0x9a, 0xcb, 0xac, 0xe1, 0x3e, 0x75, 0x31, 0x8a, 0x70, 0xf9, 0x07, 0xe5, 0xeb, 0x8a, 0xbb, 0xc4, 0x1d, 0x97, 0xf7, 0x38, 0xff, 0x83, 0xf2, 0x0c, 0xa7, 0x2e, 0xb8, 0x25,
0x05, 0xce, 0xdd, 0x70, 0x27, 0xad, 0x52, 0xbf, 0x7c, 0xe4, 0xfe, 0xe1, 0x7e, 0x18, 0x49, 0x1c, 0x8b, 0xc4, 0xcf, 0x1f, 0xb9, 0x93, 0xe7, 0x38, 0x55, 0xb5, 0xda, 0xf0, 0x71, 0xc5, 0x6f, 0x79,
0x47, 0x12, 0xa7, 0x91, 0xe0, 0xd5, 0x12, 0x7c, 0x58, 0x82, 0x4f, 0x4b, 0x30, 0x58, 0x82, 0x2f, 0xb8, 0xeb, 0x07, 0x12, 0xfb, 0x81, 0xc4, 0x61, 0x20, 0x78, 0xb5, 0x04, 0x1f, 0x96, 0xe0, 0xd3,
0x4b, 0xf0, 0x6d, 0x49, 0x9c, 0x2c, 0xc1, 0xdb, 0x44, 0x62, 0x98, 0x48, 0x1c, 0x27, 0x12, 0xc5, 0x12, 0xf4, 0x96, 0xe0, 0xcb, 0x12, 0x7c, 0x5b, 0x12, 0x07, 0x4b, 0xf0, 0x3e, 0x92, 0xe8, 0x47,
0xc2, 0x3d, 0x7e, 0xf7, 0x13, 0x00, 0x00, 0xff, 0xff, 0xb5, 0x7a, 0x39, 0x5a, 0x11, 0x01, 0x00, 0x12, 0xfb, 0x91, 0x44, 0x36, 0x73, 0x5f, 0xb8, 0xfd, 0x09, 0x00, 0x00, 0xff, 0xff, 0x06, 0x40,
0x00, 0x0b, 0xb4, 0x1e, 0x01, 0x00, 0x00,
} }
func (this *Header) Equal(that interface{}) bool { func (this *Header) Equal(that interface{}) bool {
@ -207,7 +215,7 @@ func (this *Header) Equal(that interface{}) bool {
if !bytes.Equal(this.Pk, that1.Pk) { if !bytes.Equal(this.Pk, that1.Pk) {
return false return false
} }
if !bytes.Equal(this.SenderSign, that1.SenderSign) { if !bytes.Equal(this.Sender, that1.Sender) {
return false return false
} }
if len(this.Keys) != len(that1.Keys) { if len(this.Keys) != len(that1.Keys) {
@ -242,6 +250,9 @@ func (this *WrappedKey) Equal(that interface{}) bool {
if !bytes.Equal(this.DKey, that1.DKey) { if !bytes.Equal(this.DKey, that1.DKey) {
return false return false
} }
if !bytes.Equal(this.Nonce, that1.Nonce) {
return false
}
return true return true
} }
func (this *Header) GoString() string { func (this *Header) GoString() string {
@ -253,7 +264,7 @@ func (this *Header) GoString() string {
s = append(s, "ChunkSize: "+fmt.Sprintf("%#v", this.ChunkSize)+",\n") s = append(s, "ChunkSize: "+fmt.Sprintf("%#v", this.ChunkSize)+",\n")
s = append(s, "Salt: "+fmt.Sprintf("%#v", this.Salt)+",\n") s = append(s, "Salt: "+fmt.Sprintf("%#v", this.Salt)+",\n")
s = append(s, "Pk: "+fmt.Sprintf("%#v", this.Pk)+",\n") s = append(s, "Pk: "+fmt.Sprintf("%#v", this.Pk)+",\n")
s = append(s, "SenderSign: "+fmt.Sprintf("%#v", this.SenderSign)+",\n") s = append(s, "Sender: "+fmt.Sprintf("%#v", this.Sender)+",\n")
if this.Keys != nil { if this.Keys != nil {
s = append(s, "Keys: "+fmt.Sprintf("%#v", this.Keys)+",\n") s = append(s, "Keys: "+fmt.Sprintf("%#v", this.Keys)+",\n")
} }
@ -264,9 +275,10 @@ func (this *WrappedKey) GoString() string {
if this == nil { if this == nil {
return "nil" return "nil"
} }
s := make([]string, 0, 5) s := make([]string, 0, 6)
s = append(s, "&pb.WrappedKey{") s = append(s, "&pb.WrappedKey{")
s = append(s, "DKey: "+fmt.Sprintf("%#v", this.DKey)+",\n") s = append(s, "DKey: "+fmt.Sprintf("%#v", this.DKey)+",\n")
s = append(s, "Nonce: "+fmt.Sprintf("%#v", this.Nonce)+",\n")
s = append(s, "}") s = append(s, "}")
return strings.Join(s, "") return strings.Join(s, "")
} }
@ -312,10 +324,10 @@ func (m *Header) MarshalToSizedBuffer(dAtA []byte) (int, error) {
dAtA[i] = 0x2a dAtA[i] = 0x2a
} }
} }
if len(m.SenderSign) > 0 { if len(m.Sender) > 0 {
i -= len(m.SenderSign) i -= len(m.Sender)
copy(dAtA[i:], m.SenderSign) copy(dAtA[i:], m.Sender)
i = encodeVarintHdr(dAtA, i, uint64(len(m.SenderSign))) i = encodeVarintHdr(dAtA, i, uint64(len(m.Sender)))
i-- i--
dAtA[i] = 0x22 dAtA[i] = 0x22
} }
@ -361,6 +373,13 @@ func (m *WrappedKey) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i _ = i
var l int var l int
_ = l _ = l
if len(m.Nonce) > 0 {
i -= len(m.Nonce)
copy(dAtA[i:], m.Nonce)
i = encodeVarintHdr(dAtA, i, uint64(len(m.Nonce)))
i--
dAtA[i] = 0x12
}
if len(m.DKey) > 0 { if len(m.DKey) > 0 {
i -= len(m.DKey) i -= len(m.DKey)
copy(dAtA[i:], m.DKey) copy(dAtA[i:], m.DKey)
@ -399,7 +418,7 @@ func (m *Header) Size() (n int) {
if l > 0 { if l > 0 {
n += 1 + l + sovHdr(uint64(l)) n += 1 + l + sovHdr(uint64(l))
} }
l = len(m.SenderSign) l = len(m.Sender)
if l > 0 { if l > 0 {
n += 1 + l + sovHdr(uint64(l)) n += 1 + l + sovHdr(uint64(l))
} }
@ -422,6 +441,10 @@ func (m *WrappedKey) Size() (n int) {
if l > 0 { if l > 0 {
n += 1 + l + sovHdr(uint64(l)) n += 1 + l + sovHdr(uint64(l))
} }
l = len(m.Nonce)
if l > 0 {
n += 1 + l + sovHdr(uint64(l))
}
return n return n
} }
@ -444,7 +467,7 @@ func (this *Header) String() string {
`ChunkSize:` + fmt.Sprintf("%v", this.ChunkSize) + `,`, `ChunkSize:` + fmt.Sprintf("%v", this.ChunkSize) + `,`,
`Salt:` + fmt.Sprintf("%v", this.Salt) + `,`, `Salt:` + fmt.Sprintf("%v", this.Salt) + `,`,
`Pk:` + fmt.Sprintf("%v", this.Pk) + `,`, `Pk:` + fmt.Sprintf("%v", this.Pk) + `,`,
`SenderSign:` + fmt.Sprintf("%v", this.SenderSign) + `,`, `Sender:` + fmt.Sprintf("%v", this.Sender) + `,`,
`Keys:` + repeatedStringForKeys + `,`, `Keys:` + repeatedStringForKeys + `,`,
`}`, `}`,
}, "") }, "")
@ -456,6 +479,7 @@ func (this *WrappedKey) String() string {
} }
s := strings.Join([]string{`&WrappedKey{`, s := strings.Join([]string{`&WrappedKey{`,
`DKey:` + fmt.Sprintf("%v", this.DKey) + `,`, `DKey:` + fmt.Sprintf("%v", this.DKey) + `,`,
`Nonce:` + fmt.Sprintf("%v", this.Nonce) + `,`,
`}`, `}`,
}, "") }, "")
return s return s
@ -586,7 +610,7 @@ func (m *Header) Unmarshal(dAtA []byte) error {
iNdEx = postIndex iNdEx = postIndex
case 4: case 4:
if wireType != 2 { if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field SenderSign", wireType) return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType)
} }
var byteLen int var byteLen int
for shift := uint(0); ; shift += 7 { for shift := uint(0); ; shift += 7 {
@ -613,9 +637,9 @@ func (m *Header) Unmarshal(dAtA []byte) error {
if postIndex > l { if postIndex > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
m.SenderSign = append(m.SenderSign[:0], dAtA[iNdEx:postIndex]...) m.Sender = append(m.Sender[:0], dAtA[iNdEx:postIndex]...)
if m.SenderSign == nil { if m.Sender == nil {
m.SenderSign = []byte{} m.Sender = []byte{}
} }
iNdEx = postIndex iNdEx = postIndex
case 5: case 5:
@ -736,6 +760,40 @@ func (m *WrappedKey) Unmarshal(dAtA []byte) error {
m.DKey = []byte{} m.DKey = []byte{}
} }
iNdEx = postIndex iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHdr
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthHdr
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthHdr
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Nonce = append(m.Nonce[:0], dAtA[iNdEx:postIndex]...)
if m.Nonce == nil {
m.Nonce = []byte{}
}
iNdEx = postIndex
default: default:
iNdEx = preIndex iNdEx = preIndex
skippy, err := skipHdr(dAtA[iNdEx:]) skippy, err := skipHdr(dAtA[iNdEx:])

View file

@ -19,7 +19,7 @@ message header {
uint32 chunk_size = 1; // encryption block size uint32 chunk_size = 1; // encryption block size
bytes salt = 2; // master salt (nonces are derived from this) bytes salt = 2; // master salt (nonces are derived from this)
bytes pk = 3; // ephemeral curve PK bytes pk = 3; // ephemeral curve PK
bytes sender_sign = 4; // signature block of sender bytes sender = 4; // sender signed artifacts
repeated wrapped_key keys = 5; // list of wrapped receiver blocks repeated wrapped_key keys = 5; // list of wrapped receiver blocks
} }
@ -29,4 +29,6 @@ message header {
*/ */
message wrapped_key { message wrapped_key {
bytes d_key = 1; // encrypted data key bytes d_key = 1; // encrypted data key
bytes nonce = 2; // nonce used for encryption
} }

View file

@ -26,29 +26,36 @@
// //
// Variable Length Segment: // Variable Length Segment:
// - Protobuf encoded, per-recipient wrapped keys // - Protobuf encoded, per-recipient wrapped keys
// - Shasum: 32 bytes (SHA256 of full header)
// //
// The variable length segment consists of one or more // The variable length segment consists of one or more
// recipients, each with their wrapped keys. This is encoded as // recipients, each with their individually wrapped keys.
// a protobuf message. This protobuf encoded message immediately
// follows the fixed length header.
// //
// The input data is encrypted with an expanded random 32-byte key: // The input data is encrypted with an expanded random 32-byte key:
// - Prefix_string = "Encrypt Nonce" // - hkdf-sha512 of random key, salt, context
// - datakey = SHA256(Prefix_string || header_checksum || random_key) // - the hkdf process yields a data-encryption key, nonce and hmac key.
// - The header checksum is mixed in the above process to ensure we // - we use the header checksum as the 'salt' for HKDF; this ensures that
// catch any malicious modification of the header. // any modification of the header yields different keys
//
// We also calculate the cumulative hmac-sha256 of the plaintext blocks.
// - When sender identity is present, we sign the final hmac and append
// the signature as the "trailer".
// - When sender identity is NOT present, we put random bytes as the
// "signature". ie in either case, there is a trailer.
//
// Note: If the trailer is missing from a sigtool encrypted file - the
// recipient has no guarantees of content immutability (ie tampering
// from one of the _other_ recipients).
// //
// The input data is broken up into "chunks"; each no larger than // The input data is broken up into "chunks"; each no larger than
// maxChunkSize. The default block size is "chunkSize". Each block // maxChunkSize. The default block size is "chunkSize". Each block
// is AEAD encrypted: // is AEAD encrypted:
// AEAD nonce = header.salt || block# || block-size // AEAD nonce = header.nonce || block#
// AD of AEAD = chunk length+eof marker
// //
// The encrypted block (includes the AEAD tag) length is written // The encrypted block (includes the AEAD tag) length is written
// as a big-endian 4-byte prefix. The high-order bit of this length // as a big-endian 4-byte prefix. The high-order bit of this length
// field is set for the last-block (denoting EOF). // field is set for the last-block (denoting EOF).
// //
// The encrypted blocks use an opinionated nonce length of 32 (_AEADNonceLen).
package sign package sign
@ -57,6 +64,7 @@ import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/ed25519" "crypto/ed25519"
"crypto/hmac"
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
"crypto/subtle" "crypto/subtle"
@ -64,42 +72,55 @@ import (
"fmt" "fmt"
"golang.org/x/crypto/curve25519" "golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
"hash"
"io" "io"
"os"
"github.com/opencoff/sigtool/internal/pb" "github.com/opencoff/sigtool/internal/pb"
) )
// Encryption chunk size = 4MB // Encryption chunk size = 4MB
const ( const (
chunkSize uint32 = 4 * 1048576 // The latest version of the tool's output file format
_SigtoolVersion = 3
chunkSize uint32 = 4 * 1048576 // 4 MB
maxChunkSize uint32 = 1 << 30 maxChunkSize uint32 = 1 << 30
_EOF uint32 = 1 << 31 _EOF uint32 = 1 << 31
_Magic = "SigTool" _Magic = "SigTool"
_MagicLen = len(_Magic) _MagicLen = len(_Magic)
_SigtoolVersion = 2 _FixedHdrLen = _MagicLen + 1 + 4 // 1: version, 4: len of variable segment
_AEADNonceLen = 32
_FixedHdrLen = _MagicLen + 1 + 4
_WrapReceiverNonce = "Receiver Key Nonce" _AesKeySize = 32
_WrapSenderNonce = "Sender Sig Nonce" _AEADNonceSize = 12
_EncryptNonce = "Encrypt Nonce" _SaltSize = 32
_RxNonceSize = 12 // nonce size of per-recipient encrypted blocks
_WrapReceiver = "Receiver Key"
_WrapSender = "Sender Sig"
_DataKeyExpansion = "Data Key Expansion"
) )
// Encryptor holds the encryption context // Encryptor holds the encryption context
type Encryptor struct { type Encryptor struct {
pb.Header pb.Header
key []byte // file encryption key key []byte // root key
nonce []byte // nonce for the data encrypting cipher
buf []byte // I/O buf (chunk-sized)
ae cipher.AEAD ae cipher.AEAD
hmac hash.Hash
// ephemeral key // ephemeral key
encSK []byte encSK []byte
started bool // sender identity
sender *PrivateKey
hdrsum []byte auth bool // set if the sender idetity is sent
buf []byte started bool
stream bool stream bool
} }
@ -123,27 +144,23 @@ func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) {
return nil, fmt.Errorf("encrypt: %w", err) return nil, fmt.Errorf("encrypt: %w", err)
} }
key := make([]byte, 32) key := randBuf(_AesKeySize)
salt := make([]byte, _AEADNonceLen) salt := randBuf(_SaltSize)
randRead(key)
randRead(salt)
wSig, err := wrapSenderSig(sk, key, salt)
if err != nil {
return nil, fmt.Errorf("encrypt: %w", err)
}
e := &Encryptor{ e := &Encryptor{
Header: pb.Header{ Header: pb.Header{
ChunkSize: blksz, ChunkSize: blksz,
Salt: salt, Salt: salt,
Pk: epk, Pk: epk,
SenderSign: wSig,
}, },
key: key, key: key,
encSK: esk, encSK: esk,
sender: sk,
}
if err = e.addSenderSig(sk); err != nil {
return nil, fmt.Errorf("encrypt: %w", err)
} }
return e, nil return e, nil
@ -210,8 +227,8 @@ func (e *Encryptor) start(wr io.Writer) error {
buffer := make([]byte, _FixedHdrLen+varSize+sha256.Size) buffer := make([]byte, _FixedHdrLen+varSize+sha256.Size)
fixHdr := buffer[:_FixedHdrLen] fixHdr := buffer[:_FixedHdrLen]
varHdr := buffer[_FixedHdrLen:] varHdr := buffer[_FixedHdrLen : _FixedHdrLen+varSize]
sumHdr := varHdr[varSize:] sumHdr := buffer[_FixedHdrLen+varSize:]
// Now assemble the fixed header // Now assemble the fixed header
copy(fixHdr[:], []byte(_Magic)) copy(fixHdr[:], []byte(_Magic))
@ -224,38 +241,45 @@ func (e *Encryptor) start(wr io.Writer) error {
return fmt.Errorf("encrypt: can't marshal header: %w", err) return fmt.Errorf("encrypt: can't marshal header: %w", err)
} }
// Now calculate checksum of everything
h := sha256.New() h := sha256.New()
h.Write(buffer[:_FixedHdrLen+varSize]) h.Write(buffer[:_FixedHdrLen+varSize])
h.Sum(sumHdr[:0]) cksum := h.Sum(sumHdr[:0])
// Finally write it out // now make the data encryption keys, nonces etc.
outbuf := make([]byte, sha256.Size+_AesKeySize+_AEADNonceSize)
// we mix the header checksum (and it captures the sigtool version, sender
// identity, etc.)
buf := expand(outbuf, e.key, cksum, []byte(_DataKeyExpansion))
var dkey, hmackey []byte
e.nonce, buf = buf[:_AEADNonceSize], buf[_AEADNonceSize:]
dkey, buf = buf[:_AesKeySize], buf[_AesKeySize:]
hmackey = buf
aes, err := aes.NewCipher(dkey)
if err != nil {
return fmt.Errorf("encrypt: %w", err)
}
if e.ae, err = cipher.NewGCM(aes); err != nil {
return fmt.Errorf("encrypt: %w", err)
}
// Finally write out the header
err = fullwrite(buffer, wr) err = fullwrite(buffer, wr)
if err != nil { if err != nil {
return fmt.Errorf("encrypt: %w", err) return fmt.Errorf("encrypt: %w", err)
} }
// we mix the header checksum to create the encryption key e.hmac = hmac.New(sha256.New, hmackey)
h = sha256.New() e.buf = make([]byte, e.ChunkSize+4+uint32(e.ae.Overhead()))
h.Write([]byte(_EncryptNonce))
h.Write(e.key)
h.Write(sumHdr)
key := h.Sum(nil)
aes, err := aes.NewCipher(key)
if err != nil {
return fmt.Errorf("encrypt: %w", err)
}
ae, err := cipher.NewGCMWithNonceSize(aes, _AEADNonceLen)
if err != nil {
return fmt.Errorf("encrypt: %w", err)
}
e.buf = make([]byte, e.ChunkSize+4+uint32(ae.Overhead()))
e.ae = ae
e.started = true e.started = true
debug("encrypt:\n\thdr-cksum: %x\n\taes-key: %x\n\tnonce: %x\n\thmac-key: %x\n",
cksum, dkey, e.nonce, hmackey)
return nil return nil
} }
@ -264,30 +288,70 @@ func (e *Encryptor) start(wr io.Writer) error {
// This protects the output stream from re-ordering attacks and length // This protects the output stream from re-ordering attacks and length
// modification attacks. The encoded length & block number is used as // modification attacks. The encoded length & block number is used as
// additional data in the AEAD construction. // additional data in the AEAD construction.
func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i uint32, eof bool) error { func (e *Encryptor) encrypt(pt []byte, wr io.Writer, i uint32, eof bool) error {
var z uint32 = uint32(len(buf)) var z uint32 = uint32(len(pt))
var nbuf [_AEADNonceLen]byte var nonce [_AEADNonceSize]byte
// mark last block // mark last block
if eof { if eof {
z |= _EOF z |= _EOF
} }
b := e.buf[:8] copy(nonce[:], e.nonce)
binary.BigEndian.PutUint32(b[:4], z)
binary.BigEndian.PutUint32(b[4:], i)
nonce := makeNonceV2(nbuf[:], e.Salt, b) // now change the upper bytes to track the block#; we use the len+eof as AD
binary.BigEndian.PutUint32(nonce[:4], i)
cbuf := e.buf[4:] // put the encoded length+eof at the start of the output buf
c := e.ae.Seal(cbuf[:0], nonce, buf, b[:]) b := e.buf[:4]
ctbuf := e.buf[4:]
binary.BigEndian.PutUint32(b, z)
ct := e.ae.Seal(ctbuf[:0], nonce[:], pt, b)
// total number of bytes written // total number of bytes written
n := len(c) + 4 n := len(ct) + 4
err := fullwrite(e.buf[:n], wr) err := fullwrite(e.buf[:n], wr)
if err != nil { if err != nil {
return fmt.Errorf("encrypt: %w", err) return fmt.Errorf("encrypt: %w", err)
} }
e.hmac.Write(b)
e.hmac.Write(pt)
if eof {
return e.writeTrailer(wr)
}
return nil
}
// Write a trailer:
// - if authenticating sender, sign the hmac and put the signature in the trailer
// - if not authenticating sender, write random bytes to the trailer
func (e *Encryptor) writeTrailer(wr io.Writer) error {
var tr []byte
switch e.auth {
case true:
var hmac [sha256.Size]byte
e.hmac.Sum(hmac[:0])
// We know sender is non null.
sig, err := e.sender.SignMessage(hmac[:], "")
if err != nil {
return fmt.Errorf("encrypt: trailer: %w", err)
}
tr = sig.Sig
case false:
tr = randBuf(ed25519.SignatureSize)
}
if err := fullwrite(tr, wr); err != nil {
return fmt.Errorf("encrypt: trailer %w", err)
}
return nil return nil
} }
@ -296,15 +360,17 @@ type Decryptor struct {
pb.Header pb.Header
ae cipher.AEAD ae cipher.AEAD
hmac hash.Hash
sender *PublicKey
rd io.Reader rd io.Reader
buf []byte buf []byte
hdrsum []byte nonce []byte // nonce for the data decrypting cipher
// flag set to true if sender signed the key key []byte // Decrypted root key
auth bool hdrsum []byte // cached header checksum
auth bool // flag set to true if sender signed the key
// Decrypted key
key []byte
eof bool eof bool
stream bool stream bool
} }
@ -347,14 +413,18 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) {
return nil, fmt.Errorf("decrypt: error while reading header: %w", err) return nil, fmt.Errorf("decrypt: error while reading header: %w", err)
} }
// The checksum in the header
verify := varBuf[varSize:] verify := varBuf[varSize:]
// the checksum we calculated
var csum [sha256.Size]byte
h := sha256.New() h := sha256.New()
h.Write(b[:]) h.Write(b[:])
h.Write(varBuf[:varSize]) h.Write(varBuf[:varSize])
cksum := h.Sum(nil) cksum := h.Sum(csum[:0])
if subtle.ConstantTimeCompare(verify, cksum[:]) == 0 { if subtle.ConstantTimeCompare(verify, cksum) == 0 {
return nil, ErrBadHeader return nil, ErrBadHeader
} }
@ -372,7 +442,7 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) {
return nil, fmt.Errorf("decrypt: invalid chunkSize %d", d.ChunkSize) return nil, fmt.Errorf("decrypt: invalid chunkSize %d", d.ChunkSize)
} }
if len(d.Salt) != _AEADNonceLen { if len(d.Salt) != _SaltSize {
return nil, fmt.Errorf("decrypt: invalid nonce length %d", len(d.Salt)) return nil, fmt.Errorf("decrypt: invalid nonce length %d", len(d.Salt))
} }
@ -382,7 +452,7 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) {
// sanity check on the wrapped keys // sanity check on the wrapped keys
for i, w := range d.Keys { for i, w := range d.Keys {
if len(w.DKey) <= 32 { if len(w.DKey) <= _AesKeySize {
return nil, fmt.Errorf("decrypt: wrapped key %d: wrong-size encrypted key", i) return nil, fmt.Errorf("decrypt: wrapped key %d: wrong-size encrypted key", i)
} }
} }
@ -402,6 +472,8 @@ func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error {
return fmt.Errorf("decrypt: can't unwrap key %d: %w", i, err) return fmt.Errorf("decrypt: can't unwrap key %d: %w", i, err)
} }
if key != nil { if key != nil {
d.key = key
d.sender = senderPk
goto havekey goto havekey
} }
} }
@ -409,28 +481,37 @@ func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error {
return ErrBadKey return ErrBadKey
havekey: havekey:
if err := d.verifySender(key, sk, senderPk); err != nil { if err := d.verifySender(key, senderPk); err != nil {
return fmt.Errorf("decrypt: %w", err) return fmt.Errorf("decrypt: %w", err)
} }
d.key = key outbuf := make([]byte, sha256.Size+_AesKeySize+_AEADNonceSize)
// we mix the header checksum into the key buf := expand(outbuf, d.key, d.hdrsum, []byte(_DataKeyExpansion))
h := sha256.New()
h.Write([]byte(_EncryptNonce))
h.Write(d.key)
h.Write(d.hdrsum)
key = h.Sum(nil)
aes, err := aes.NewCipher(key) var dkey, hmackey []byte
d.nonce, buf = buf[:_AEADNonceSize], buf[_AEADNonceSize:]
dkey, buf = buf[:_AesKeySize], buf[_AesKeySize:]
hmackey = buf
d.hmac = hmac.New(sha256.New, hmackey)
aes, err := aes.NewCipher(dkey)
if err != nil { if err != nil {
return fmt.Errorf("decrypt: %w", err) return fmt.Errorf("decrypt: %w", err)
} }
d.ae, err = cipher.NewGCMWithNonceSize(aes, _AEADNonceLen) d.ae, err = cipher.NewGCM(aes)
if err != nil { if err != nil {
return fmt.Errorf("decrypt: %w", err) return fmt.Errorf("decrypt: %w", err)
} }
debug("decrypt:\n\thdr-cksum: %x\n\taes-key: %x\n\tnonce: %x\n\thmac-key: %x\n",
d.hdrsum, dkey, d.nonce, hmackey)
// We have a separate on-stack buffer for reading the header (4 bytes).
// Thus, the actual I/O buf will never be larger than the chunksize + AEAD Overhead
d.buf = make([]byte, int(d.ChunkSize)+d.ae.Overhead()) d.buf = make([]byte, int(d.ChunkSize)+d.ae.Overhead())
return nil return nil
} }
@ -477,17 +558,16 @@ func (d *Decryptor) Decrypt(wr io.Writer) error {
// Decrypt exactly one chunk of data // Decrypt exactly one chunk of data
func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) { func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
var b [8]byte
var nonceb [32]byte
var ovh uint32 = uint32(d.ae.Overhead()) var ovh uint32 = uint32(d.ae.Overhead())
var p []byte var b [4]byte
var nonce [_AEADNonceSize]byte
n, err := io.ReadFull(d.rd, b[:4]) n, err := io.ReadFull(d.rd, b[:])
if err != nil || n == 0 { if err != nil || n == 0 {
return nil, false, fmt.Errorf("decrypt: premature EOF while reading header block %d", i) return nil, false, fmt.Errorf("decrypt: premature EOF while reading header block %d", i)
} }
m := binary.BigEndian.Uint32(b[:4]) m := binary.BigEndian.Uint32(b[:])
eof := (m & _EOF) > 0 eof := (m & _EOF) > 0
m &= (_EOF - 1) m &= (_EOF - 1)
@ -500,13 +580,14 @@ func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
if !eof { if !eof {
return nil, false, fmt.Errorf("decrypt: block %d: zero-sized chunk without EOF", i) return nil, false, fmt.Errorf("decrypt: block %d: zero-sized chunk without EOF", i)
} }
return p, eof, nil return nil, eof, nil
default: default:
} }
binary.BigEndian.PutUint32(b[4:], i) // make the nonce - top 4 bytes are the counter
nonce := makeNonceV2(nonceb[:], d.Salt, b[:]) copy(nonce[:], d.nonce)
binary.BigEndian.PutUint32(nonce[:4], i)
z := m + ovh z := m + ovh
n, err = io.ReadFull(d.rd, d.buf[:z]) n, err = io.ReadFull(d.rd, d.buf[:z])
@ -514,57 +595,115 @@ func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
return nil, false, fmt.Errorf("decrypt: premature EOF while reading block %d: %w", i, err) return nil, false, fmt.Errorf("decrypt: premature EOF while reading block %d: %w", i, err)
} }
p, err = d.ae.Open(d.buf[:0], nonce, d.buf[:n], b[:]) pt, err := d.ae.Open(d.buf[:0], nonce[:], d.buf[:n], b[:])
if err != nil { if err != nil {
return nil, false, fmt.Errorf("decrypt: can't decrypt chunk %d: %w", i, err) return nil, false, fmt.Errorf("decrypt: can't decrypt chunk %d: %w", i, err)
} }
return p[:m], eof, nil if uint32(len(pt)) != m {
return nil, false, fmt.Errorf("decrypt: partial unsealed bytes; exp %d, saw %d", m, len(pt))
} }
// Wrap sender's signature of the encryption key d.hmac.Write(b[:])
// if sender has provided their identity to authenticate, we sign the data-enc key d.hmac.Write(pt)
// and encrypt the signature. At no point will we send the sender's identity.
func wrapSenderSig(sk *PrivateKey, key, salt []byte) ([]byte, error) { if eof {
return d.processTrailer(pt, eof)
}
return pt, eof, nil
}
func (d *Decryptor) processTrailer(pt []byte, eof bool) ([]byte, bool, error) {
var rd [ed25519.SignatureSize]byte
_, err := io.ReadFull(d.rd, rd[:])
if err != nil {
return nil, false, fmt.Errorf("decrypt: premature EOF while reading trailer: %w", err)
}
if !d.auth {
// these are random bytes; ignore em
return pt, eof, nil
}
var hmac [sha256.Size]byte
cksum := d.hmac.Sum(hmac[:0])
ss := &Signature{
Sig: rd[:],
}
if ok := d.sender.VerifyMessage(cksum, ss); !ok {
return nil, eof, ErrBadTrailer
}
return pt, eof, nil
}
// optionally sign the checksum and encrypt everything
func (e *Encryptor) addSenderSig(sk *PrivateKey) error {
var zero [ed25519.SignatureSize]byte var zero [ed25519.SignatureSize]byte
var sig []byte var auth bool
sig := zero[:]
switch { if e.sender != nil {
case sk == nil: var csum [sha256.Size]byte
sig = zero[:]
default: // We capture essential meta-data from the sender; viz:
xsig, err := sk.SignMessage(key, "") // - Sender tool version
// - Sender generated curve25519 PK
// - session salt, root key
h := sha256.New()
h.Write([]byte(_Magic))
h.Write([]byte{_SigtoolVersion})
h.Write(e.Pk)
h.Write(e.Salt)
h.Write(e.key)
cksum := h.Sum(csum[:0])
xsig, err := e.sender.SignMessage(cksum, "")
if err != nil { if err != nil {
return nil, fmt.Errorf("wrap: can't sign: %w", err) return fmt.Errorf("wrap: can't sign: %w", err)
} }
sig = xsig.Sig sig = xsig.Sig
auth = true
} }
aes, err := aes.NewCipher(key) buf := make([]byte, _AesKeySize+_AEADNonceSize)
buf = expand(buf, e.key, e.Salt, []byte(_WrapSender))
ekey, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
aes, err := aes.NewCipher(ekey)
if err != nil { if err != nil {
return nil, fmt.Errorf("wrap: %w", err) return fmt.Errorf("senderId: %w", err)
} }
ae, err := cipher.NewGCM(aes) ae, err := cipher.NewGCM(aes)
if err != nil { if err != nil {
return nil, fmt.Errorf("wrap: %w", err) return fmt.Errorf("senderId: %w", err)
} }
tagsize := ae.Overhead() outbuf := make([]byte, ed25519.SignatureSize+ae.Overhead())
nonceSize := ae.NonceSize() buf = ae.Seal(outbuf[:0], nonce, sig, nil)
nonce := sha256Slices([]byte(_WrapSenderNonce), salt)[:nonceSize] e.auth = auth
esig := make([]byte, tagsize+len(sig)) e.Sender = buf
return ae.Seal(esig[:0], nonce, sig, nil), nil return nil
} }
// unwrap sender's signature using 'key' and extract the signature // unwrap sender's signature using 'key' and extract the signature
// Optionally, verify the signature using the sender's PK (if provided). // Optionally, verify the signature using the sender's PK (if provided).
func (d *Decryptor) verifySender(key []byte, sk *PrivateKey, senderPK *PublicKey) error { func (d *Decryptor) verifySender(key []byte, senderPk *PublicKey) error {
aes, err := aes.NewCipher(key) outbuf := make([]byte, _AEADNonceSize+_AesKeySize)
buf := expand(outbuf, key, d.Salt, []byte(_WrapSender))
ekey, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
aes, err := aes.NewCipher(ekey)
if err != nil { if err != nil {
return fmt.Errorf("unwrap: %w", err) return fmt.Errorf("unwrap: %w", err)
} }
@ -574,31 +713,42 @@ func (d *Decryptor) verifySender(key []byte, sk *PrivateKey, senderPK *PublicKey
return fmt.Errorf("unwrap: %w", err) return fmt.Errorf("unwrap: %w", err)
} }
nonceSize := ae.NonceSize() var sigbuf [ed25519.SignatureSize]byte
nonce := sha256Slices([]byte(_WrapSenderNonce), d.Salt)[:nonceSize] var zero [ed25519.SignatureSize]byte
sig := make([]byte, ed25519.SignatureSize)
sig, err = ae.Open(sig[:0], nonce, d.SenderSign, nil) sig, err := ae.Open(sigbuf[:0], nonce, d.Sender, nil)
if err != nil { if err != nil {
return fmt.Errorf("unwrap: can't open sender info: %w", err) return fmt.Errorf("unwrap: can't open sender info: %w", err)
} }
var zero [ed25519.SignatureSize]byte
// Did the sender actually sign anything? // Did the sender actually sign anything?
if subtle.ConstantTimeCompare(zero[:], sig) == 0 { if subtle.ConstantTimeCompare(zero[:], sig) == 0 {
// we set this to indicate that the sender authenticated themselves; if senderPk == nil {
d.auth = true return ErrNoSenderPK
}
var csum [sha256.Size]byte
h := sha256.New()
h.Write([]byte(_Magic))
h.Write([]byte{_SigtoolVersion})
h.Write(d.Pk)
h.Write(d.Salt)
h.Write(key)
cksum := h.Sum(csum[:0])
if senderPK != nil {
ss := &Signature{ ss := &Signature{
Sig: sig, Sig: sig,
} }
if ok := senderPK.VerifyMessage(key, ss); !ok { if ok := senderPk.VerifyMessage(cksum, ss); !ok {
return fmt.Errorf("unwrap: sender verification failed") return ErrBadSender
}
} }
// we set this to indicate that the sender authenticated themselves;
d.auth = true
} }
return nil return nil
} }
@ -606,12 +756,26 @@ func (d *Decryptor) verifySender(key []byte, sk *PrivateKey, senderPK *PublicKey
// basically, we do a scalarmult: Ephemeral encryption/decryption SK x receiver PK // basically, we do a scalarmult: Ephemeral encryption/decryption SK x receiver PK
func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) { func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
rxPK := pk.ToCurve25519PK() rxPK := pk.ToCurve25519PK()
dkek, err := curve25519.X25519(e.encSK, rxPK) sekrit, err := curve25519.X25519(e.encSK, rxPK)
if err != nil { if err != nil {
return nil, fmt.Errorf("wrap: %w", err) return nil, fmt.Errorf("wrap: %w", err)
} }
aes, err := aes.NewCipher(dkek) var shasum [sha256.Size]byte
rbuf := randBuf(_RxNonceSize)
h := sha256.New()
h.Write(e.Salt)
h.Write(rbuf[:])
h.Sum(shasum[:0])
out := make([]byte, _AesKeySize+_RxNonceSize)
buf := expand(out[:], sekrit, shasum[:], []byte(_WrapReceiver))
kek, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
aes, err := aes.NewCipher(kek)
if err != nil { if err != nil {
return nil, fmt.Errorf("wrap: %w", err) return nil, fmt.Errorf("wrap: %w", err)
} }
@ -621,14 +785,10 @@ func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
return nil, fmt.Errorf("wrap: %w", err) return nil, fmt.Errorf("wrap: %w", err)
} }
tagsize := ae.Overhead() ekey := make([]byte, ae.Overhead()+len(e.key))
nonceSize := ae.NonceSize()
nonceR := sha256Slices([]byte(_WrapReceiverNonce), e.Salt)[:nonceSize]
ekey := make([]byte, tagsize+len(e.key))
w := &pb.WrappedKey{ w := &pb.WrappedKey{
DKey: ae.Seal(ekey[:0], nonceR, e.key, pk.Pk), DKey: ae.Seal(ekey[:0], nonce, e.key, pk.Pk),
Nonce: rbuf,
} }
return w, nil return w, nil
@ -638,40 +798,48 @@ func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
// senders ephemeral PublicKey // senders ephemeral PublicKey
func (d *Decryptor) unwrapKey(w *pb.WrappedKey, sk *PrivateKey) ([]byte, error) { func (d *Decryptor) unwrapKey(w *pb.WrappedKey, sk *PrivateKey) ([]byte, error) {
ourSK := sk.ToCurve25519SK() ourSK := sk.ToCurve25519SK()
dkek, err := curve25519.X25519(ourSK, d.Pk) sekrit, err := curve25519.X25519(ourSK, d.Pk)
if err != nil { if err != nil {
return nil, fmt.Errorf("unwrap: %w", err) return nil, fmt.Errorf("unwrap: %w", err)
} }
aes, err := aes.NewCipher(dkek) var shasum [sha256.Size]byte
h := sha256.New()
h.Write(d.Salt)
h.Write(w.Nonce)
h.Sum(shasum[:0])
out := make([]byte, _AesKeySize+_RxNonceSize)
buf := expand(out[:], sekrit, shasum[:], []byte(_WrapReceiver))
kek, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
aes, err := aes.NewCipher(kek)
if err != nil { if err != nil {
return nil, fmt.Errorf("unwrap: %w", err) return nil, fmt.Errorf("wrap: %w", err)
} }
ae, err := cipher.NewGCM(aes) ae, err := cipher.NewGCM(aes)
if err != nil { if err != nil {
return nil, fmt.Errorf("unwrap: %w", err) return nil, fmt.Errorf("wrap: %w", err)
} }
// 32 == AES-256 key size want := _AesKeySize + ae.Overhead()
want := 32 + ae.Overhead()
if len(w.DKey) != want { if len(w.DKey) != want {
return nil, fmt.Errorf("unwrap: incorrect decrypt bytes (need %d, saw %d)", want, len(w.DKey)) return nil, fmt.Errorf("unwrap: incorrect decrypt bytes (need %d, saw %d)", want, len(w.DKey))
} }
nonceSize := ae.NonceSize()
nonceR := sha256Slices([]byte(_WrapReceiverNonce), d.Salt)[:nonceSize]
pk := sk.PublicKey() pk := sk.PublicKey()
dkey := make([]byte, _AesKeySize) // decrypted data decryption key
dkey := make([]byte, 32) // decrypted data decryption key
// we indicate incorrect receiver SK by returning a nil key // we indicate incorrect receiver SK by returning a nil key
dkey, err = ae.Open(dkey[:0], nonceR, w.DKey, pk.Pk) dkey, err = ae.Open(dkey[:0], nonce, w.DKey, pk.Pk)
if err != nil { if err != nil {
return nil, nil return nil, nil
} }
// we have successfully found the correct recipient
return dkey, nil return dkey, nil
} }
@ -691,30 +859,14 @@ func fullwrite(buf []byte, wr io.Writer) error {
return nil return nil
} }
// make aead nonce from salt, chunk-size and block#
// First 8 bytes are chunk-size and nonce (in 'ad')
func makeNonceV2(dest []byte, salt []byte, ad []byte) []byte {
n := len(ad)
copy(dest, ad)
copy(dest[n:], salt)
return dest
}
// make aead nonce from salt, chunk-size and block# for v1
// This is here for historical documentation purposes
func makeNonceV1(dest []byte, salt []byte, ad []byte) []byte {
h := sha256.New()
h.Write(salt)
h.Write(ad)
return h.Sum(dest[:0])
}
// generate a KEK from a shared DH key and a Pub Key // generate a KEK from a shared DH key and a Pub Key
func expand(shared, pk []byte) ([]byte, error) { func expand(out []byte, shared, salt, ad []byte) []byte {
kek := make([]byte, 32) h := hkdf.New(sha512.New, shared, salt, ad)
h := hkdf.New(sha512.New, shared, pk, nil) _, err := io.ReadFull(h, out)
_, err := io.ReadFull(h, kek) if err != nil {
return kek, err panic(fmt.Sprintf("hkdf: failed to generate %d bytes: %s", len(out), err))
}
return out
} }
func newSender() (sk, pk []byte, err error) { func newSender() (sk, pk []byte, err error) {
@ -736,4 +888,25 @@ func sha256Slices(v ...[]byte) []byte {
return h.Sum(nil)[:] return h.Sum(nil)[:]
} }
var _debug int = 0
// Enable debugging of this module;
// level > 0 elicits debug messages on os.Stderr
func Debug(level int) {
_debug = level
}
func debug(s string, v ...interface{}) {
if _debug <= 0 {
return
}
z := fmt.Sprintf(s, v...)
if n := len(z); z[n-1] != '\n' {
z += "\n"
}
os.Stderr.WriteString(z)
os.Stderr.Sync()
}
// EOF // EOF

View file

@ -20,17 +20,19 @@ import (
var ( var (
ErrClosed = errors.New("encrypt: stream already closed") ErrClosed = errors.New("encrypt: stream already closed")
ErrNoKey = errors.New("decrypt: No private key set for decryption") ErrNoKey = errors.New("decrypt: no private key set for decryption")
ErrEncStarted = errors.New("encrypt: can't add new recipient after encryption has started") ErrEncStarted = errors.New("encrypt: can't add new recipient after encryption has started")
ErrDecStarted = errors.New("decrypt: can't add new recipient after decryption has started") ErrDecStarted = errors.New("decrypt: can't add new recipient after decryption has started")
ErrEncIsStream = errors.New("encrypt: can't use Encrypt() after using streaming I/O") ErrEncIsStream = errors.New("encrypt: can't use Encrypt() after using streaming I/O")
ErrNotSigTool = errors.New("decrypt: Not a sigtool encrypted file?") ErrNotSigTool = errors.New("decrypt: not a sigtool encrypted file?")
ErrHeaderTooBig = errors.New("decrypt: header too large (max 1048576)") ErrHeaderTooBig = errors.New("decrypt: header too large (max 1048576)")
ErrHeaderTooSmall = errors.New("decrypt: header too small (min 32)") ErrHeaderTooSmall = errors.New("decrypt: header too small (min 32)")
ErrBadHeader = errors.New("decrypt: header corrupted") ErrBadHeader = errors.New("decrypt: header corrupted")
ErrNoWrappedKeys = errors.New("decrypt: No wrapped keys in encrypted file") ErrNoWrappedKeys = errors.New("decrypt: no wrapped keys in encrypted file")
ErrBadKey = errors.New("decrypt: wrong key") ErrBadKey = errors.New("decrypt: wrong key")
ErrBadTrailer = errors.New("decrypt: message integrity failed (bad trailer)")
ErrBadSender = errors.New("unwrap: sender verification failed") ErrBadSender = errors.New("unwrap: sender verification failed")
ErrNoSenderPK = errors.New("unwrap: missing sender public key")
ErrIncorrectPassword = errors.New("ssh: invalid passphrase") ErrIncorrectPassword = errors.New("ssh: invalid passphrase")
ErrNoPEMFound = errors.New("ssh: no PEM block found") ErrNoPEMFound = errors.New("ssh: no PEM block found")

View file

@ -38,3 +38,8 @@ func randRead(b []byte) []byte {
} }
return b return b
} }
func randBuf(sz int) []byte {
b := make([]byte, sz)
return randRead(b)
}

View file

@ -30,12 +30,13 @@ var Z string = path.Base(os.Args[0])
func main() { func main() {
var ver, help bool var ver, help, debug bool
mf := flag.NewFlagSet(Z, flag.ExitOnError) mf := flag.NewFlagSet(Z, flag.ExitOnError)
mf.SetInterspersed(false) mf.SetInterspersed(false)
mf.BoolVarP(&ver, "version", "v", false, "Show version info and exit") mf.BoolVarP(&ver, "version", "v", false, "Show version info and exit")
mf.BoolVarP(&help, "help", "h", false, "Show help info exit") mf.BoolVarP(&help, "help", "h", false, "Show help info exit")
mf.BoolVarP(&debug, "debug", "", false, "Enable debug mode")
mf.Parse(os.Args[1:]) mf.Parse(os.Args[1:])
if ver { if ver {
@ -80,6 +81,10 @@ func main() {
Die("can't map command %s", canon) Die("can't map command %s", canon)
} }
if debug {
sign.Debug(1)
}
cmd(args[1:]) cmd(args[1:])
// always call Exit so that at-exit handlers are called. // always call Exit so that at-exit handlers are called.
@ -323,7 +328,8 @@ Usage: %s [global-options] command [options] arg [args..]
Global options: Global options:
-h, --help Show help and exit -h, --help Show help and exit
-v, --version Show version info and exit. -v, --version Show version info and exit
--debug Enable debug (DANGEROUS)
Commands: Commands:
generate, g Generate a new Ed25519 keypair generate, g Generate a new Ed25519 keypair