diff --git a/internal/pb/hdr.pb.go b/internal/pb/hdr.pb.go index 8645be9..3a1d597 100644 --- a/internal/pb/hdr.pb.go +++ b/internal/pb/hdr.pb.go @@ -31,11 +31,11 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // decoded version of this information. It is encoded in // protobuf format before writing to disk. type Header struct { - 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"` - 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"` - Keys []*WrappedKey `protobuf:"bytes,5,rep,name=keys,proto3" json:"keys,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"` + Pk []byte `protobuf:"bytes,3,opt,name=pk,proto3" json:"pk,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"` } func (m *Header) Reset() { *m = Header{} } @@ -91,9 +91,9 @@ func (m *Header) GetPk() []byte { return nil } -func (m *Header) GetSenderSign() []byte { +func (m *Header) GetSender() []byte { if m != nil { - return m.SenderSign + return m.Sender } return nil } @@ -109,7 +109,8 @@ func (m *Header) GetKeys() []*WrappedKey { // A file encryption key is wrapped by a recipient specific public // key. WrappedKey describes such a wrapped key. 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{} } @@ -151,6 +152,13 @@ func (m *WrappedKey) GetDKey() []byte { return nil } +func (m *WrappedKey) GetNonce() []byte { + if m != nil { + return m.Nonce + } + return nil +} + func init() { proto.RegisterType((*Header)(nil), "pb.header") proto.RegisterType((*WrappedKey)(nil), "pb.wrapped_key") @@ -159,24 +167,24 @@ func init() { func init() { proto.RegisterFile("internal/pb/hdr.proto", fileDescriptor_c715362029a696e2) } var fileDescriptor_c715362029a696e2 = []byte{ - // 257 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0xcf, 0x31, 0x4e, 0xeb, 0x40, - 0x10, 0xc6, 0xf1, 0x1d, 0xc7, 0x89, 0xf4, 0x26, 0x79, 0x20, 0x2d, 0x42, 0x72, 0xc3, 0x60, 0x99, - 0xc6, 0x95, 0x23, 0x01, 0x27, 0xa0, 0xa5, 0x73, 0x0e, 0x60, 0xd9, 0x78, 0x14, 0x5b, 0x8e, 0x36, - 0xab, 0xb5, 0x11, 0x72, 0x2a, 0x8e, 0x00, 0xb7, 0xe0, 0x28, 0x94, 0x2e, 0x53, 0xe2, 0x75, 0x43, - 0x99, 0x23, 0x20, 0x2d, 0x0d, 0xdd, 0xa7, 0xdf, 0xbf, 0x99, 0xc1, 0xcb, 0x5a, 0x75, 0x6c, 0x54, - 0xbe, 0x5b, 0xeb, 0x62, 0x5d, 0x95, 0x26, 0xd1, 0x66, 0xdf, 0xed, 0xa5, 0xa7, 0x8b, 0xe8, 0x1d, - 0x70, 0x51, 0x71, 0x5e, 0xb2, 0x91, 0x57, 0x88, 0x4f, 0xd5, 0xb3, 0x6a, 0xb2, 0xb6, 0x3e, 0x70, - 0x00, 0x21, 0xc4, 0xff, 0xd3, 0x7f, 0x4e, 0x36, 0xf5, 0x81, 0xa5, 0x44, 0xbf, 0xcd, 0x77, 0x5d, - 0xe0, 0x85, 0x10, 0xaf, 0x52, 0xb7, 0xe5, 0x19, 0x7a, 0xba, 0x09, 0x66, 0x4e, 0x3c, 0xdd, 0xc8, - 0x6b, 0x5c, 0xb6, 0xac, 0x4a, 0x36, 0x59, 0x5b, 0x6f, 0x55, 0xe0, 0xbb, 0x80, 0xbf, 0xb4, 0xa9, - 0xb7, 0x4a, 0xde, 0xa0, 0xdf, 0x70, 0xdf, 0x06, 0xf3, 0x70, 0x16, 0x2f, 0x6f, 0xcf, 0x13, 0x5d, - 0x24, 0x2f, 0x26, 0xd7, 0x9a, 0xcb, 0xac, 0xe1, 0x3e, 0x75, 0x31, 0x8a, 0x70, 0xf9, 0x07, 0xe5, - 0x05, 0xce, 0xdd, 0x70, 0x27, 0xad, 0x52, 0xbf, 0x7c, 0xe4, 0xfe, 0xe1, 0x7e, 0x18, 0x49, 0x1c, - 0x47, 0x12, 0xa7, 0x91, 0xe0, 0xd5, 0x12, 0x7c, 0x58, 0x82, 0x4f, 0x4b, 0x30, 0x58, 0x82, 0x2f, - 0x4b, 0xf0, 0x6d, 0x49, 0x9c, 0x2c, 0xc1, 0xdb, 0x44, 0x62, 0x98, 0x48, 0x1c, 0x27, 0x12, 0xc5, - 0xc2, 0x3d, 0x7e, 0xf7, 0x13, 0x00, 0x00, 0xff, 0xff, 0xb5, 0x7a, 0x39, 0x5a, 0x11, 0x01, 0x00, - 0x00, + // 262 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0xd0, 0x3f, 0x4e, 0xf3, 0x40, + 0x10, 0x05, 0xf0, 0x1d, 0xc7, 0x89, 0xf4, 0x4d, 0xf2, 0x81, 0xb4, 0xfc, 0x91, 0x1b, 0x46, 0x56, + 0x68, 0x5c, 0x39, 0x12, 0x50, 0x50, 0xd3, 0xd2, 0x99, 0x03, 0x44, 0x76, 0x3c, 0x92, 0x2d, 0x47, + 0xeb, 0xd5, 0xda, 0x08, 0x39, 0x15, 0x25, 0x25, 0xc7, 0xe0, 0x28, 0x94, 0x2e, 0x53, 0xe2, 0x75, + 0x43, 0x99, 0x23, 0x20, 0x2d, 0x29, 0xe8, 0xde, 0xfb, 0x4d, 0xf3, 0x34, 0x78, 0x51, 0xaa, 0x96, + 0x8d, 0x4a, 0xb7, 0x2b, 0x9d, 0xad, 0x8a, 0xdc, 0xc4, 0xda, 0xd4, 0x6d, 0x2d, 0x3d, 0x9d, 0x2d, + 0xdf, 0x00, 0x67, 0x05, 0xa7, 0x39, 0x1b, 0x79, 0x85, 0xb8, 0x29, 0x9e, 0x55, 0xb5, 0x6e, 0xca, + 0x1d, 0x07, 0x10, 0x42, 0xf4, 0x3f, 0xf9, 0xe7, 0xe4, 0xa9, 0xdc, 0xb1, 0x94, 0xe8, 0x37, 0xe9, + 0xb6, 0x0d, 0xbc, 0x10, 0xa2, 0x45, 0xe2, 0xb2, 0x3c, 0x41, 0x4f, 0x57, 0xc1, 0xc4, 0x89, 0xa7, + 0x2b, 0x79, 0x89, 0xb3, 0x86, 0x55, 0xce, 0x26, 0xf0, 0x9d, 0x1d, 0x9b, 0xbc, 0x46, 0xbf, 0xe2, + 0xae, 0x09, 0xa6, 0xe1, 0x24, 0x9a, 0xdf, 0x9c, 0xc6, 0x3a, 0x8b, 0x5f, 0x4c, 0xaa, 0x35, 0xe7, + 0xeb, 0x8a, 0xbb, 0xc4, 0x1d, 0x97, 0xf7, 0x38, 0xff, 0x83, 0xf2, 0x0c, 0xa7, 0x2e, 0xb8, 0x25, + 0x8b, 0xc4, 0xcf, 0x1f, 0xb9, 0x93, 0xe7, 0x38, 0x55, 0xb5, 0xda, 0xf0, 0x71, 0xc5, 0x6f, 0x79, + 0xb8, 0xeb, 0x07, 0x12, 0xfb, 0x81, 0xc4, 0x61, 0x20, 0x78, 0xb5, 0x04, 0x1f, 0x96, 0xe0, 0xd3, + 0x12, 0xf4, 0x96, 0xe0, 0xcb, 0x12, 0x7c, 0x5b, 0x12, 0x07, 0x4b, 0xf0, 0x3e, 0x92, 0xe8, 0x47, + 0x12, 0xfb, 0x91, 0x44, 0x36, 0x73, 0x5f, 0xb8, 0xfd, 0x09, 0x00, 0x00, 0xff, 0xff, 0x06, 0x40, + 0x0b, 0xb4, 0x1e, 0x01, 0x00, 0x00, } 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) { return false } - if !bytes.Equal(this.SenderSign, that1.SenderSign) { + if !bytes.Equal(this.Sender, that1.Sender) { return false } 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) { return false } + if !bytes.Equal(this.Nonce, that1.Nonce) { + return false + } return true } 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, "Salt: "+fmt.Sprintf("%#v", this.Salt)+",\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 { s = append(s, "Keys: "+fmt.Sprintf("%#v", this.Keys)+",\n") } @@ -264,9 +275,10 @@ func (this *WrappedKey) GoString() string { if this == nil { return "nil" } - s := make([]string, 0, 5) + s := make([]string, 0, 6) s = append(s, "&pb.WrappedKey{") s = append(s, "DKey: "+fmt.Sprintf("%#v", this.DKey)+",\n") + s = append(s, "Nonce: "+fmt.Sprintf("%#v", this.Nonce)+",\n") s = append(s, "}") return strings.Join(s, "") } @@ -312,10 +324,10 @@ func (m *Header) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x2a } } - if len(m.SenderSign) > 0 { - i -= len(m.SenderSign) - copy(dAtA[i:], m.SenderSign) - i = encodeVarintHdr(dAtA, i, uint64(len(m.SenderSign))) + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintHdr(dAtA, i, uint64(len(m.Sender))) i-- dAtA[i] = 0x22 } @@ -361,6 +373,13 @@ func (m *WrappedKey) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = 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 { i -= len(m.DKey) copy(dAtA[i:], m.DKey) @@ -399,7 +418,7 @@ func (m *Header) Size() (n int) { if l > 0 { n += 1 + l + sovHdr(uint64(l)) } - l = len(m.SenderSign) + l = len(m.Sender) if l > 0 { n += 1 + l + sovHdr(uint64(l)) } @@ -422,6 +441,10 @@ func (m *WrappedKey) Size() (n int) { if l > 0 { n += 1 + l + sovHdr(uint64(l)) } + l = len(m.Nonce) + if l > 0 { + n += 1 + l + sovHdr(uint64(l)) + } return n } @@ -444,7 +467,7 @@ func (this *Header) String() string { `ChunkSize:` + fmt.Sprintf("%v", this.ChunkSize) + `,`, `Salt:` + fmt.Sprintf("%v", this.Salt) + `,`, `Pk:` + fmt.Sprintf("%v", this.Pk) + `,`, - `SenderSign:` + fmt.Sprintf("%v", this.SenderSign) + `,`, + `Sender:` + fmt.Sprintf("%v", this.Sender) + `,`, `Keys:` + repeatedStringForKeys + `,`, `}`, }, "") @@ -456,6 +479,7 @@ func (this *WrappedKey) String() string { } s := strings.Join([]string{`&WrappedKey{`, `DKey:` + fmt.Sprintf("%v", this.DKey) + `,`, + `Nonce:` + fmt.Sprintf("%v", this.Nonce) + `,`, `}`, }, "") return s @@ -586,7 +610,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 4: 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 for shift := uint(0); ; shift += 7 { @@ -613,9 +637,9 @@ func (m *Header) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.SenderSign = append(m.SenderSign[:0], dAtA[iNdEx:postIndex]...) - if m.SenderSign == nil { - m.SenderSign = []byte{} + m.Sender = append(m.Sender[:0], dAtA[iNdEx:postIndex]...) + if m.Sender == nil { + m.Sender = []byte{} } iNdEx = postIndex case 5: @@ -736,6 +760,40 @@ func (m *WrappedKey) Unmarshal(dAtA []byte) error { m.DKey = []byte{} } 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: iNdEx = preIndex skippy, err := skipHdr(dAtA[iNdEx:]) diff --git a/internal/pb/hdr.proto b/internal/pb/hdr.proto index 21122c2..a5a7c52 100644 --- a/internal/pb/hdr.proto +++ b/internal/pb/hdr.proto @@ -16,11 +16,11 @@ package pb; * protobuf format before writing to disk. */ message header { - uint32 chunk_size = 1; // encryption block size - bytes salt = 2; // master salt (nonces are derived from this) - bytes pk = 3; // ephemeral curve PK - bytes sender_sign = 4; // signature block of sender - repeated wrapped_key keys = 5; // list of wrapped receiver blocks + uint32 chunk_size = 1; // encryption block size + bytes salt = 2; // master salt (nonces are derived from this) + bytes pk = 3; // ephemeral curve PK + bytes sender = 4; // sender signed artifacts + repeated wrapped_key keys = 5; // list of wrapped receiver blocks } /* @@ -28,5 +28,7 @@ message header { * key. WrappedKey describes such a 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 } + diff --git a/sign/encrypt.go b/sign/encrypt.go index 5cb16e3..86229f0 100644 --- a/sign/encrypt.go +++ b/sign/encrypt.go @@ -26,29 +26,36 @@ // // Variable Length Segment: // - Protobuf encoded, per-recipient wrapped keys -// - Shasum: 32 bytes (SHA256 of full header) // // The variable length segment consists of one or more -// recipients, each with their wrapped keys. This is encoded as -// a protobuf message. This protobuf encoded message immediately -// follows the fixed length header. +// recipients, each with their individually wrapped keys. // // The input data is encrypted with an expanded random 32-byte key: -// - Prefix_string = "Encrypt Nonce" -// - datakey = SHA256(Prefix_string || header_checksum || random_key) -// - The header checksum is mixed in the above process to ensure we -// catch any malicious modification of the header. +// - hkdf-sha512 of random key, salt, context +// - the hkdf process yields a data-encryption key, nonce and hmac key. +// - we use the header checksum as the 'salt' for HKDF; this ensures that +// 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 // maxChunkSize. The default block size is "chunkSize". Each block // 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 // as a big-endian 4-byte prefix. The high-order bit of this length // field is set for the last-block (denoting EOF). // -// The encrypted blocks use an opinionated nonce length of 32 (_AEADNonceLen). package sign @@ -57,6 +64,7 @@ import ( "crypto/aes" "crypto/cipher" "crypto/ed25519" + "crypto/hmac" "crypto/sha256" "crypto/sha512" "crypto/subtle" @@ -64,43 +72,56 @@ import ( "fmt" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/hkdf" + "hash" "io" + "os" "github.com/opencoff/sigtool/internal/pb" ) // Encryption chunk size = 4MB 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 _EOF uint32 = 1 << 31 - _Magic = "SigTool" - _MagicLen = len(_Magic) - _SigtoolVersion = 2 - _AEADNonceLen = 32 - _FixedHdrLen = _MagicLen + 1 + 4 + _Magic = "SigTool" + _MagicLen = len(_Magic) + _FixedHdrLen = _MagicLen + 1 + 4 // 1: version, 4: len of variable segment - _WrapReceiverNonce = "Receiver Key Nonce" - _WrapSenderNonce = "Sender Sig Nonce" - _EncryptNonce = "Encrypt Nonce" + _AesKeySize = 32 + _AEADNonceSize = 12 + _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 type Encryptor struct { pb.Header - key []byte // file encryption key + key []byte // root key - ae cipher.AEAD + nonce []byte // nonce for the data encrypting cipher + buf []byte // I/O buf (chunk-sized) + + ae cipher.AEAD + hmac hash.Hash // ephemeral key encSK []byte - started bool + // sender identity + sender *PrivateKey - hdrsum []byte - buf []byte - stream bool + auth bool // set if the sender idetity is sent + started bool + stream bool } // Create a new Encryption context for encrypting blocks of size 'blksize'. @@ -123,27 +144,23 @@ func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) { return nil, fmt.Errorf("encrypt: %w", err) } - key := make([]byte, 32) - salt := make([]byte, _AEADNonceLen) - - randRead(key) - randRead(salt) - - wSig, err := wrapSenderSig(sk, key, salt) - if err != nil { - return nil, fmt.Errorf("encrypt: %w", err) - } + key := randBuf(_AesKeySize) + salt := randBuf(_SaltSize) e := &Encryptor{ Header: pb.Header{ - ChunkSize: blksz, - Salt: salt, - Pk: epk, - SenderSign: wSig, + ChunkSize: blksz, + Salt: salt, + Pk: epk, }, - key: key, - encSK: esk, + key: key, + encSK: esk, + sender: sk, + } + + if err = e.addSenderSig(sk); err != nil { + return nil, fmt.Errorf("encrypt: %w", err) } return e, nil @@ -210,8 +227,8 @@ func (e *Encryptor) start(wr io.Writer) error { buffer := make([]byte, _FixedHdrLen+varSize+sha256.Size) fixHdr := buffer[:_FixedHdrLen] - varHdr := buffer[_FixedHdrLen:] - sumHdr := varHdr[varSize:] + varHdr := buffer[_FixedHdrLen : _FixedHdrLen+varSize] + sumHdr := buffer[_FixedHdrLen+varSize:] // Now assemble the fixed header 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) } - // Now calculate checksum of everything h := sha256.New() 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) if err != nil { return fmt.Errorf("encrypt: %w", err) } - // we mix the header checksum to create the encryption key - h = sha256.New() - 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.hmac = hmac.New(sha256.New, hmackey) + e.buf = make([]byte, e.ChunkSize+4+uint32(e.ae.Overhead())) 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 } @@ -264,30 +288,70 @@ func (e *Encryptor) start(wr io.Writer) error { // This protects the output stream from re-ordering attacks and length // modification attacks. The encoded length & block number is used as // additional data in the AEAD construction. -func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i uint32, eof bool) error { - var z uint32 = uint32(len(buf)) - var nbuf [_AEADNonceLen]byte +func (e *Encryptor) encrypt(pt []byte, wr io.Writer, i uint32, eof bool) error { + var z uint32 = uint32(len(pt)) + var nonce [_AEADNonceSize]byte // mark last block if eof { z |= _EOF } - b := e.buf[:8] - binary.BigEndian.PutUint32(b[:4], z) - binary.BigEndian.PutUint32(b[4:], i) + copy(nonce[:], e.nonce) - 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:] - c := e.ae.Seal(cbuf[:0], nonce, buf, b[:]) + // put the encoded length+eof at the start of the output buf + 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 - n := len(c) + 4 + n := len(ct) + 4 err := fullwrite(e.buf[:n], wr) if err != nil { 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 } @@ -295,16 +359,18 @@ func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i uint32, eof bool) error type Decryptor struct { pb.Header - ae cipher.AEAD - rd io.Reader - buf []byte - hdrsum []byte + ae cipher.AEAD + hmac hash.Hash - // flag set to true if sender signed the key - auth bool + sender *PublicKey - // Decrypted key - key []byte + rd io.Reader + buf []byte + nonce []byte // nonce for the data decrypting cipher + + key []byte // Decrypted root key + hdrsum []byte // cached header checksum + auth bool // flag set to true if sender signed the key eof 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) } + // The checksum in the header verify := varBuf[varSize:] + // the checksum we calculated + var csum [sha256.Size]byte + h := sha256.New() h.Write(b[:]) 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 } @@ -372,7 +442,7 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) { 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)) } @@ -382,7 +452,7 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) { // sanity check on the wrapped 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) } } @@ -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) } if key != nil { + d.key = key + d.sender = senderPk goto havekey } } @@ -409,28 +481,37 @@ func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error { return ErrBadKey havekey: - if err := d.verifySender(key, sk, senderPk); err != nil { + if err := d.verifySender(key, senderPk); err != nil { return fmt.Errorf("decrypt: %w", err) } - d.key = key + outbuf := make([]byte, sha256.Size+_AesKeySize+_AEADNonceSize) - // we mix the header checksum into the key - h := sha256.New() - h.Write([]byte(_EncryptNonce)) - h.Write(d.key) - h.Write(d.hdrsum) - key = h.Sum(nil) + buf := expand(outbuf, d.key, d.hdrsum, []byte(_DataKeyExpansion)) - 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 { return fmt.Errorf("decrypt: %w", err) } - d.ae, err = cipher.NewGCMWithNonceSize(aes, _AEADNonceLen) + d.ae, err = cipher.NewGCM(aes) if err != nil { 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()) return nil } @@ -477,17 +558,16 @@ func (d *Decryptor) Decrypt(wr io.Writer) error { // Decrypt exactly one chunk of data 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 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 { 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 m &= (_EOF - 1) @@ -500,13 +580,14 @@ func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) { if !eof { return nil, false, fmt.Errorf("decrypt: block %d: zero-sized chunk without EOF", i) } - return p, eof, nil + return nil, eof, nil default: } - binary.BigEndian.PutUint32(b[4:], i) - nonce := makeNonceV2(nonceb[:], d.Salt, b[:]) + // make the nonce - top 4 bytes are the counter + copy(nonce[:], d.nonce) + binary.BigEndian.PutUint32(nonce[:4], i) z := m + ovh 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) } - 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 { return nil, false, fmt.Errorf("decrypt: can't decrypt chunk %d: %w", i, err) } - return p[:m], eof, nil -} - -// Wrap sender's signature of the encryption key -// if sender has provided their identity to authenticate, we sign the data-enc key -// and encrypt the signature. At no point will we send the sender's identity. -func wrapSenderSig(sk *PrivateKey, key, salt []byte) ([]byte, error) { - var zero [ed25519.SignatureSize]byte - var sig []byte - - switch { - case sk == nil: - sig = zero[:] - - default: - xsig, err := sk.SignMessage(key, "") - if err != nil { - return nil, fmt.Errorf("wrap: can't sign: %w", err) - } - - sig = xsig.Sig + if uint32(len(pt)) != m { + return nil, false, fmt.Errorf("decrypt: partial unsealed bytes; exp %d, saw %d", m, len(pt)) } - aes, err := aes.NewCipher(key) + d.hmac.Write(b[:]) + d.hmac.Write(pt) + + 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, fmt.Errorf("wrap: %w", err) + 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 auth bool + sig := zero[:] + + if e.sender != nil { + var csum [sha256.Size]byte + + // We capture essential meta-data from the sender; viz: + // - 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 { + return fmt.Errorf("wrap: can't sign: %w", err) + } + sig = xsig.Sig + auth = true + } + + 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 { + return fmt.Errorf("senderId: %w", err) } ae, err := cipher.NewGCM(aes) if err != nil { - return nil, fmt.Errorf("wrap: %w", err) + return fmt.Errorf("senderId: %w", err) } - tagsize := ae.Overhead() - nonceSize := ae.NonceSize() + outbuf := make([]byte, ed25519.SignatureSize+ae.Overhead()) + buf = ae.Seal(outbuf[:0], nonce, sig, nil) - nonce := sha256Slices([]byte(_WrapSenderNonce), salt)[:nonceSize] - esig := make([]byte, tagsize+len(sig)) + e.auth = auth + e.Sender = buf - return ae.Seal(esig[:0], nonce, sig, nil), nil + return nil } // unwrap sender's signature using 'key' and extract the signature // Optionally, verify the signature using the sender's PK (if provided). -func (d *Decryptor) verifySender(key []byte, sk *PrivateKey, senderPK *PublicKey) error { - aes, err := aes.NewCipher(key) +func (d *Decryptor) verifySender(key []byte, senderPk *PublicKey) error { + 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 { 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) } - nonceSize := ae.NonceSize() - nonce := sha256Slices([]byte(_WrapSenderNonce), d.Salt)[:nonceSize] - sig := make([]byte, ed25519.SignatureSize) - sig, err = ae.Open(sig[:0], nonce, d.SenderSign, nil) + var sigbuf [ed25519.SignatureSize]byte + var zero [ed25519.SignatureSize]byte + + sig, err := ae.Open(sigbuf[:0], nonce, d.Sender, nil) if err != nil { return fmt.Errorf("unwrap: can't open sender info: %w", err) } - var zero [ed25519.SignatureSize]byte - // Did the sender actually sign anything? if subtle.ConstantTimeCompare(zero[:], sig) == 0 { + if senderPk == nil { + 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]) + + ss := &Signature{ + Sig: sig, + } + + if ok := senderPk.VerifyMessage(cksum, ss); !ok { + return ErrBadSender + } + // we set this to indicate that the sender authenticated themselves; d.auth = true - - if senderPK != nil { - ss := &Signature{ - Sig: sig, - } - - if ok := senderPK.VerifyMessage(key, ss); !ok { - return fmt.Errorf("unwrap: sender verification failed") - } - } } + 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 func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) { rxPK := pk.ToCurve25519PK() - dkek, err := curve25519.X25519(e.encSK, rxPK) + sekrit, err := curve25519.X25519(e.encSK, rxPK) if err != nil { 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 { 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) } - tagsize := ae.Overhead() - nonceSize := ae.NonceSize() - - nonceR := sha256Slices([]byte(_WrapReceiverNonce), e.Salt)[:nonceSize] - ekey := make([]byte, tagsize+len(e.key)) - + ekey := make([]byte, ae.Overhead()+len(e.key)) 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 @@ -638,40 +798,48 @@ func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) { // senders ephemeral PublicKey func (d *Decryptor) unwrapKey(w *pb.WrappedKey, sk *PrivateKey) ([]byte, error) { ourSK := sk.ToCurve25519SK() - dkek, err := curve25519.X25519(ourSK, d.Pk) + sekrit, err := curve25519.X25519(ourSK, d.Pk) if err != nil { 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 { - return nil, fmt.Errorf("unwrap: %w", err) + return nil, fmt.Errorf("wrap: %w", err) } ae, err := cipher.NewGCM(aes) if err != nil { - return nil, fmt.Errorf("unwrap: %w", err) + return nil, fmt.Errorf("wrap: %w", err) } - // 32 == AES-256 key size - want := 32 + ae.Overhead() + want := _AesKeySize + ae.Overhead() if len(w.DKey) != want { 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() - - dkey := make([]byte, 32) // decrypted data decryption key + dkey := make([]byte, _AesKeySize) // decrypted data decryption 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 { return nil, nil } + // we have successfully found the correct recipient return dkey, nil } @@ -691,30 +859,14 @@ func fullwrite(buf []byte, wr io.Writer) error { 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 -func expand(shared, pk []byte) ([]byte, error) { - kek := make([]byte, 32) - h := hkdf.New(sha512.New, shared, pk, nil) - _, err := io.ReadFull(h, kek) - return kek, err +func expand(out []byte, shared, salt, ad []byte) []byte { + h := hkdf.New(sha512.New, shared, salt, ad) + _, err := io.ReadFull(h, out) + if err != nil { + panic(fmt.Sprintf("hkdf: failed to generate %d bytes: %s", len(out), err)) + } + return out } func newSender() (sk, pk []byte, err error) { @@ -736,4 +888,25 @@ func sha256Slices(v ...[]byte) []byte { 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 diff --git a/sign/errors.go b/sign/errors.go index f039939..91fbd28 100644 --- a/sign/errors.go +++ b/sign/errors.go @@ -20,17 +20,19 @@ import ( var ( 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") 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") - 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)") ErrHeaderTooSmall = errors.New("decrypt: header too small (min 32)") 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") + ErrBadTrailer = errors.New("decrypt: message integrity failed (bad trailer)") ErrBadSender = errors.New("unwrap: sender verification failed") + ErrNoSenderPK = errors.New("unwrap: missing sender public key") ErrIncorrectPassword = errors.New("ssh: invalid passphrase") ErrNoPEMFound = errors.New("ssh: no PEM block found") diff --git a/sign/rand.go b/sign/rand.go index e571783..e607773 100644 --- a/sign/rand.go +++ b/sign/rand.go @@ -38,3 +38,8 @@ func randRead(b []byte) []byte { } return b } + +func randBuf(sz int) []byte { + b := make([]byte, sz) + return randRead(b) +} diff --git a/sigtool.go b/sigtool.go index 697d69b..ef7b7f5 100644 --- a/sigtool.go +++ b/sigtool.go @@ -30,12 +30,13 @@ var Z string = path.Base(os.Args[0]) func main() { - var ver, help bool + var ver, help, debug bool mf := flag.NewFlagSet(Z, flag.ExitOnError) mf.SetInterspersed(false) mf.BoolVarP(&ver, "version", "v", false, "Show version info and exit") mf.BoolVarP(&help, "help", "h", false, "Show help info exit") + mf.BoolVarP(&debug, "debug", "", false, "Enable debug mode") mf.Parse(os.Args[1:]) if ver { @@ -80,8 +81,12 @@ func main() { Die("can't map command %s", canon) } + if debug { + sign.Debug(1) + } + cmd(args[1:]) - + // always call Exit so that at-exit handlers are called. Exit(0) } @@ -323,7 +328,8 @@ Usage: %s [global-options] command [options] arg [args..] Global options: -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: generate, g Generate a new Ed25519 keypair