diff --git a/cmd/stbak/cmd/archive.go b/cmd/stbak/cmd/archive.go index 6933b23..9010c98 100644 --- a/cmd/stbak/cmd/archive.go +++ b/cmd/stbak/cmd/archive.go @@ -349,6 +349,10 @@ func archive( hdrToAppend := *hdr headers = append(headers, &hdrToAppend) + if err := signHeader(hdr, signatureFormat, identity); err != nil { + return err + } + if err := encryptHeader(hdr, encryptionFormat, recipient); err != nil { return err } @@ -472,7 +476,38 @@ func encryptHeader( return err } - newHdr.PAXRecords[pax.STFSEmbeddedHeader], err = encryptString(string(wrappedHeader), encryptionFormat, recipient) + newHdr.PAXRecords[pax.STFSRecordEmbeddedHeader], err = encryptString(string(wrappedHeader), encryptionFormat, recipient) + if err != nil { + return err + } + + *hdr = *newHdr + + return nil +} + +func signHeader( + hdr *tar.Header, + signatureFormat string, + identity interface{}, +) error { + if signatureFormat == noneKey { + return nil + } + + newHdr := &tar.Header{ + Format: tar.FormatPAX, + Size: hdr.Size, + PAXRecords: map[string]string{}, + } + + wrappedHeader, err := json.Marshal(hdr) + if err != nil { + return err + } + + newHdr.PAXRecords[pax.STFSRecordEmbeddedHeader] = string(wrappedHeader) + newHdr.PAXRecords[pax.STFSRecordSignature], err = signString(newHdr.PAXRecords[pax.STFSRecordEmbeddedHeader], signatureFormat, identity) if err != nil { return err } @@ -655,6 +690,26 @@ func encryptString( } } +func signString( + src string, + signatureFormat string, + identity interface{}, +) (string, error) { + switch signatureFormat { + case signatureFormatMinisignKey: + identity, ok := identity.(minisign.PrivateKey) + if !ok { + return "", errIdentityUnparsable + } + + return base64.StdEncoding.EncodeToString(minisign.Sign(identity, []byte(src))), nil + case noneKey: + return src, nil + default: + return "", errUnsupportedSignatureFormat + } +} + func compress( dst io.Writer, compressionFormat string, diff --git a/cmd/stbak/cmd/recovery_fetch.go b/cmd/stbak/cmd/recovery_fetch.go index b1d23b7..de28e41 100644 --- a/cmd/stbak/cmd/recovery_fetch.go +++ b/cmd/stbak/cmd/recovery_fetch.go @@ -43,6 +43,8 @@ var ( errIdentityUnparsable = errors.New("recipient could not be parsed") errInvalidSignature = errors.New("invalid signature") + + errSignatureMissing = errors.New("missing signature") ) var recoveryFetchCmd = &cobra.Command{ @@ -157,6 +159,10 @@ func restoreFromRecordAndBlock( return err } + if err := verifyHeader(hdr, signatureFormat, recipient); err != nil { + return err + } + if showHeader { if err := formatting.PrintCSV(formatting.TARHeaderCSV); err != nil { return err @@ -297,7 +303,7 @@ func decryptHeader( return errEmbeddedHeaderMissing } - encryptedEmbeddedHeader, ok := hdr.PAXRecords[pax.STFSEmbeddedHeader] + encryptedEmbeddedHeader, ok := hdr.PAXRecords[pax.STFSRecordEmbeddedHeader] if !ok { return errEmbeddedHeaderMissing } @@ -317,6 +323,43 @@ func decryptHeader( return nil } +func verifyHeader( + hdr *tar.Header, + signatureFormat string, + recipient interface{}, +) error { + if signatureFormat == noneKey { + return nil + } + + if hdr.PAXRecords == nil { + return errEmbeddedHeaderMissing + } + + embeddedHeader, ok := hdr.PAXRecords[pax.STFSRecordEmbeddedHeader] + if !ok { + return errEmbeddedHeaderMissing + } + + signature, ok := hdr.PAXRecords[pax.STFSRecordSignature] + if !ok { + return errSignatureMissing + } + + if err := verifyString(embeddedHeader, signatureFormat, recipient, signature); err != nil { + return err + } + + var newHdr tar.Header + if err := json.Unmarshal([]byte(embeddedHeader), &newHdr); err != nil { + return err + } + + *hdr = newHdr + + return nil +} + func parseIdentity( encryptionFormat string, privkey []byte, @@ -525,6 +568,36 @@ func verify( } } +func verifyString( + src string, + signatureFormat string, + recipient interface{}, + signature string, +) error { + switch signatureFormat { + case signatureFormatMinisignKey: + recipient, ok := recipient.(minisign.PublicKey) + if !ok { + return errRecipientUnparsable + } + + decodedSignature, err := base64.StdEncoding.DecodeString(signature) + if err != nil { + return err + } + + if minisign.Verify(recipient, []byte(src), decodedSignature) { + return nil + } + + return errInvalidSignature + case noneKey: + return nil + default: + return errUnsupportedSignatureFormat + } +} + func init() { recoveryFetchCmd.PersistentFlags().IntP(recordSizeFlag, "z", 20, "Amount of 512-bit blocks per record") recoveryFetchCmd.PersistentFlags().IntP(recordFlag, "k", 0, "Record to seek too") diff --git a/pkg/pax/stfs.go b/pkg/pax/stfs.go index f978151..a52d117 100644 --- a/pkg/pax/stfs.go +++ b/pkg/pax/stfs.go @@ -23,7 +23,7 @@ const ( STFSRecordSignature = STFSPrefix + "Signature" - STFSEmbeddedHeader = STFSPrefix + "EmbeddedHeader" + STFSRecordEmbeddedHeader = STFSPrefix + "EmbeddedHeader" ) var (