2017-01-22 11:15:17 -05:00
/ *
2017-09-12 09:59:23 -07:00
Package sops manages JSON , YAML and BINARY documents to be encrypted or decrypted .
2017-01-22 11:15:17 -05:00
This package should not be used directly . Instead , Sops users should install the
2023-07-11 21:09:23 +02:00
command line client via ` go get -u github.com/getsops/sops/v3/cmd/sops ` , or use the
decryption helper provided at ` github.com/getsops/sops/v3/decrypt ` .
2017-01-22 11:15:17 -05:00
2023-07-11 21:09:23 +02:00
We do not guarantee API stability for any package other than ` github.com/getsops/sops/v3/decrypt ` .
2017-08-23 17:15:34 -07:00
2017-01-22 11:15:17 -05:00
A Sops document is a Tree composed of a data branch with arbitrary key / value pairs
and a metadata branch with encryption and integrity information .
In JSON and YAML formats , the structure of the cleartext tree is preserved , keys are
2024-06-26 17:16:16 +02:00
stored in cleartext and only values are encrypted . Keeping the keys in cleartext
2017-01-22 11:15:17 -05:00
provides better readability when storing Sops documents in version controls , and allows
for merging competing changes on documents . This is a major difference between Sops
and other encryption tools that store documents as encrypted blobs .
In BINARY format , the cleartext data is treated as a single blob and the encrypted
document is in JSON format with a single ` data ` key and a single encrypted value .
Sops allows operators to encrypt their documents with multiple master keys . Each of
the master key defined in the document is able to decrypt it , allowing users to
share documents amongst themselves without sharing keys , or using a PGP key as a
backup for KMS .
In practice , this is achieved by generating a data key for each document that is used
to encrypt all values , and encrypting the data with each master key defined . Being
able to decrypt the data key gives access to the document .
2019-09-11 23:15:55 +01:00
The integrity of each document is guaranteed by calculating a Message Authentication Code
( MAC ) that is stored encrypted by the data key . When decrypting a document , the MAC should
2017-01-22 11:15:17 -05:00
be recalculated and compared with the MAC stored in the document to verify that no
fraudulent changes have been applied . The MAC covers keys and values as well as their
ordering .
* /
2023-11-07 19:28:34 +02:00
package sops // import "github.com/getsops/sops/v3"
2016-08-11 11:44:00 -07:00
import (
2016-08-29 10:10:02 -07:00
"crypto/rand"
2016-08-19 15:35:41 -07:00
"crypto/sha512"
2016-08-11 11:44:00 -07:00
"fmt"
2016-08-26 12:01:06 -07:00
"reflect"
2019-08-14 15:39:21 -04:00
"regexp"
2024-03-29 22:28:01 +01:00
"slices"
2023-11-07 19:28:34 +02:00
"sort"
2016-08-19 15:35:41 -07:00
"strconv"
"strings"
2016-08-11 11:44:00 -07:00
"time"
2016-10-28 09:40:43 -07:00
2023-11-07 19:28:34 +02:00
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
"github.com/getsops/sops/v3/age"
2023-07-11 21:09:23 +02:00
"github.com/getsops/sops/v3/audit"
"github.com/getsops/sops/v3/keys"
"github.com/getsops/sops/v3/keyservice"
"github.com/getsops/sops/v3/logging"
2023-11-07 19:28:34 +02:00
"github.com/getsops/sops/v3/pgp"
2023-07-11 21:09:23 +02:00
"github.com/getsops/sops/v3/shamir"
2016-08-11 11:44:00 -07:00
)
2016-08-24 10:29:28 -07:00
// DefaultUnencryptedSuffix is the default suffix a TreeItem key has to end with for sops to leave its Value unencrypted
2016-08-18 15:49:27 -07:00
const DefaultUnencryptedSuffix = "_unencrypted"
2023-11-07 19:28:34 +02:00
var DefaultDecryptionOrder = [ ] string { age . KeyTypeIdentifier , pgp . KeyTypeIdentifier }
2016-08-24 10:29:28 -07:00
type sopsError string
2016-08-17 11:51:56 -07:00
2017-05-26 11:17:43 +02:00
func ( e sopsError ) Error ( ) string {
return string ( e )
}
2016-08-17 11:51:56 -07:00
2016-08-24 10:29:28 -07:00
// MacMismatch occurs when the computed MAC does not match the expected ones
const MacMismatch = sopsError ( "MAC mismatch" )
2016-08-17 11:51:56 -07:00
2016-08-29 08:51:37 -07:00
// MetadataNotFound occurs when the input file is malformed and doesn't have sops metadata in it
const MetadataNotFound = sopsError ( "sops metadata not found" )
2024-03-31 12:53:08 +02:00
type SopsKeyNotFound struct {
Key interface { }
Msg string
}
func ( e * SopsKeyNotFound ) Error ( ) string {
return fmt . Sprintf ( e . Msg , e . Key )
}
2021-12-18 21:50:57 +01:00
// MACOnlyEncryptedInitialization is a constant and known sequence of 32 bytes used to initialize
// MAC which is computed only over values which end up encrypted. That assures that a MAC with the
// setting enabled is always different from a MAC with this setting disabled.
// The following numbers are taken from the output of `echo -n sops | sha256sum` (shell) or `hashlib.sha256(b'sops').hexdigest()` (Python).
var MACOnlyEncryptedInitialization = [ ] byte { 0x8a , 0x3f , 0xd2 , 0xad , 0x54 , 0xce , 0x66 , 0x52 , 0x7b , 0x10 , 0x34 , 0xf3 , 0xd1 , 0x47 , 0xbe , 0xb , 0xb , 0x97 , 0x5b , 0x3b , 0xf4 , 0x4f , 0x72 , 0xc6 , 0xfd , 0xad , 0xec , 0x81 , 0x76 , 0xf2 , 0x7d , 0x69 }
2017-09-06 17:36:39 -07:00
var log * logrus . Logger
func init ( ) {
2017-09-07 10:49:27 -07:00
log = logging . NewLogger ( "SOPS" )
2017-09-06 17:36:39 -07:00
}
2017-09-12 20:41:02 -07:00
// Cipher provides a way to encrypt and decrypt the data key used to encrypt and decrypt sops files, so that the
// data key can be stored alongside the encrypted content. A Cipher must be able to decrypt the values it encrypts.
type Cipher interface {
// Encrypt takes a plaintext, a key and additional data and returns the plaintext encrypted with the key, using the
// additional data for authentication
Encrypt ( plaintext interface { } , key [ ] byte , additionalData string ) ( ciphertext string , err error )
// Encrypt takes a ciphertext, a key and additional data and returns the ciphertext encrypted with the key, using
// the additional data for authentication
Decrypt ( ciphertext string , key [ ] byte , additionalData string ) ( plaintext interface { } , err error )
2016-08-24 14:38:05 -07:00
}
2016-10-26 16:57:00 +02:00
// Comment represents a comment in the sops tree for the file formats that actually support them.
type Comment struct {
Value string
}
2016-08-24 10:29:28 -07:00
// TreeItem is an item inside sops's tree
2016-08-19 15:35:41 -07:00
type TreeItem struct {
2016-10-20 14:48:38 -07:00
Key interface { }
2016-08-19 15:35:41 -07:00
Value interface { }
}
2016-08-24 10:29:28 -07:00
// TreeBranch is a branch inside sops's tree. It is a slice of TreeItems and is therefore ordered
2016-08-19 15:35:41 -07:00
type TreeBranch [ ] TreeItem
2019-07-08 15:32:33 -07:00
// TreeBranches is a collection of TreeBranch
2019-07-11 10:29:52 -07:00
// Trees usually have more than one branch
2018-09-20 11:18:34 -06:00
type TreeBranches [ ] TreeBranch
2018-02-20 17:15:02 +01:00
func valueFromPathAndLeaf ( path [ ] interface { } , leaf interface { } ) interface { } {
switch component := path [ 0 ] . ( type ) {
case int :
if len ( path ) == 1 {
return [ ] interface { } {
leaf ,
}
2019-07-08 15:32:33 -07:00
}
return [ ] interface { } {
valueFromPathAndLeaf ( path [ 1 : ] , leaf ) ,
2018-02-20 17:15:02 +01:00
}
default :
if len ( path ) == 1 {
return TreeBranch {
TreeItem {
2018-04-08 12:43:43 +03:00
Key : component ,
2018-02-20 17:15:02 +01:00
Value : leaf ,
} ,
}
2019-07-08 15:32:33 -07:00
}
return TreeBranch {
TreeItem {
Key : component ,
Value : valueFromPathAndLeaf ( path [ 1 : ] , leaf ) ,
} ,
2016-11-03 17:41:38 +01:00
}
}
2018-02-20 17:15:02 +01:00
}
func set ( branch interface { } , path [ ] interface { } , value interface { } ) interface { } {
switch branch := branch . ( type ) {
case TreeBranch :
for i , item := range branch {
if item . Key == path [ 0 ] {
if len ( path ) == 1 {
branch [ i ] . Value = value
} else {
branch [ i ] . Value = set ( item . Value , path [ 1 : ] , value )
}
return branch
}
}
// Not found, need to add the next path entry to the branch
2021-07-02 14:42:07 +02:00
value := valueFromPathAndLeaf ( path , value )
if newBranch , ok := value . ( TreeBranch ) ; ok && len ( newBranch ) > 0 {
return append ( branch , newBranch [ 0 ] )
2018-02-20 17:15:02 +01:00
}
2021-07-02 14:42:07 +02:00
return branch
2018-02-20 17:15:02 +01:00
case [ ] interface { } :
position := path [ 0 ] . ( int )
if len ( path ) == 1 {
2018-02-20 23:32:19 +01:00
if position >= len ( branch ) {
2018-02-20 17:15:02 +01:00
return append ( branch , value )
}
2019-07-08 15:32:33 -07:00
branch [ position ] = value
2018-02-20 17:15:02 +01:00
} else {
if position >= len ( branch ) {
branch = append ( branch , valueFromPathAndLeaf ( path [ 1 : ] , value ) )
}
2019-07-08 15:32:33 -07:00
branch [ position ] = set ( branch [ position ] , path [ 1 : ] , value )
2018-02-20 17:15:02 +01:00
}
2019-07-08 15:32:33 -07:00
return branch
2018-02-20 17:15:02 +01:00
default :
return valueFromPathAndLeaf ( path , value )
2016-11-03 17:41:38 +01:00
}
2018-02-20 17:15:02 +01:00
}
2019-07-08 15:32:33 -07:00
// Set sets a value on a given tree for the specified path
2018-04-08 12:43:43 +03:00
func ( branch TreeBranch ) Set ( path [ ] interface { } , value interface { } ) TreeBranch {
2018-02-20 17:15:02 +01:00
return set ( branch , path , value ) . ( TreeBranch )
2016-11-03 17:41:38 +01:00
}
2024-03-29 22:28:01 +01:00
func unset ( branch interface { } , path [ ] interface { } ) ( interface { } , error ) {
switch branch := branch . ( type ) {
case TreeBranch :
for i , item := range branch {
if item . Key == path [ 0 ] {
if len ( path ) == 1 {
branch = slices . Delete ( branch , i , i + 1 )
} else {
v , err := unset ( item . Value , path [ 1 : ] )
if err != nil {
return nil , err
}
branch [ i ] . Value = v
}
return branch , nil
}
}
2024-03-31 12:53:08 +02:00
return nil , & SopsKeyNotFound { Msg : "Key not found: %s" , Key : path [ 0 ] }
2024-03-29 22:28:01 +01:00
case [ ] interface { } :
position := path [ 0 ] . ( int )
if position >= len ( branch ) {
2024-03-31 12:53:08 +02:00
return nil , & SopsKeyNotFound { Msg : "Index %d out of bounds" , Key : path [ 0 ] }
2024-03-29 22:28:01 +01:00
}
if len ( path ) == 1 {
branch = slices . Delete ( branch , position , position + 1 )
} else {
v , err := unset ( branch [ position ] , path [ 1 : ] )
if err != nil {
return nil , err
}
branch [ position ] = v
}
return branch , nil
default :
2024-03-31 12:53:08 +02:00
return nil , fmt . Errorf ( "Unsupported type: %T for item '%s'" , branch , path [ 0 ] )
2024-03-29 22:28:01 +01:00
}
}
// Unset removes a value on a given tree from the specified path
func ( branch TreeBranch ) Unset ( path [ ] interface { } ) ( TreeBranch , error ) {
v , err := unset ( branch , path )
if err != nil {
return nil , err
}
return v . ( TreeBranch ) , nil
}
2016-08-24 10:29:28 -07:00
// Tree is the data structure used by sops to represent documents internally
2016-08-22 14:04:31 -07:00
type Tree struct {
Metadata Metadata
2018-09-20 11:18:34 -06:00
Branches TreeBranches
2018-04-22 12:21:58 -07:00
// FilePath is the path of the file this struct represents
FilePath string
2016-08-22 14:04:31 -07:00
}
2017-08-24 12:04:28 -07:00
// Truncate truncates the tree to the path specified
2017-09-12 09:59:23 -07:00
func ( branch TreeBranch ) Truncate ( path [ ] interface { } ) ( interface { } , error ) {
2017-09-07 10:49:27 -07:00
log . WithField ( "path" , path ) . Info ( "Truncating tree" )
2017-09-12 09:59:23 -07:00
var current interface { } = branch
2017-08-24 12:04:28 -07:00
for _ , component := range path {
switch component := component . ( type ) {
case string :
found := false
2016-08-26 12:01:06 -07:00
for _ , item := range current . ( TreeBranch ) {
if item . Key == component {
current = item . Value
2017-08-24 12:04:28 -07:00
found = true
2016-08-26 12:01:06 -07:00
break
}
}
2017-08-24 12:04:28 -07:00
if ! found {
return nil , fmt . Errorf ( "component ['%s'] not found" , component )
}
case int :
if reflect . ValueOf ( current ) . Kind ( ) != reflect . Slice {
return nil , fmt . Errorf ( "component [%d] is integer, but tree part is not a slice" , component )
}
if reflect . ValueOf ( current ) . Len ( ) <= component {
return nil , fmt . Errorf ( "component [%d] accesses out of bounds" , component )
}
current = reflect . ValueOf ( current ) . Index ( component ) . Interface ( )
2016-08-26 12:01:06 -07:00
}
}
return current , nil
}
2021-12-20 00:03:19 +01:00
func ( branch TreeBranch ) walkValue ( in interface { } , path [ ] string , commentsStack [ ] [ ] string , onLeaves func ( in interface { } , path [ ] string , commentsStack [ ] [ ] string ) ( interface { } , error ) ) ( interface { } , error ) {
2016-08-19 15:35:41 -07:00
switch in := in . ( type ) {
case string :
2021-12-20 00:03:19 +01:00
return onLeaves ( in , path , commentsStack )
2016-09-06 16:06:27 -07:00
case [ ] byte :
2021-12-20 00:03:19 +01:00
return onLeaves ( string ( in ) , path , commentsStack )
2016-08-19 15:35:41 -07:00
case int :
2021-12-20 00:03:19 +01:00
return onLeaves ( in , path , commentsStack )
2016-08-19 15:35:41 -07:00
case bool :
2021-12-20 00:03:19 +01:00
return onLeaves ( in , path , commentsStack )
2016-08-24 17:12:46 -07:00
case float64 :
2021-12-20 00:03:19 +01:00
return onLeaves ( in , path , commentsStack )
2017-08-15 13:48:55 -07:00
case Comment :
2021-12-20 00:03:19 +01:00
return onLeaves ( in , path , commentsStack )
2016-08-19 15:35:41 -07:00
case TreeBranch :
2021-12-20 00:03:19 +01:00
return branch . walkBranch ( in , path , commentsStack , onLeaves )
2016-08-19 15:35:41 -07:00
case [ ] interface { } :
2021-12-20 00:03:19 +01:00
return branch . walkSlice ( in , path , commentsStack , onLeaves )
2017-09-16 21:54:52 +02:00
case nil :
// the value returned remains the same since it doesn't make
// sense to encrypt or decrypt a nil value
return nil , nil
2016-08-19 15:35:41 -07:00
default :
return nil , fmt . Errorf ( "Cannot walk value, unknown type: %T" , in )
}
}
2021-12-20 00:03:19 +01:00
func ( branch TreeBranch ) walkSlice ( in [ ] interface { } , path [ ] string , commentsStack [ ] [ ] string , onLeaves func ( in interface { } , path [ ] string , commentsStack [ ] [ ] string ) ( interface { } , error ) ) ( [ ] interface { } , error ) {
// Because append returns a new slice, the original stack is not changed.
commentsStack = append ( commentsStack , [ ] string { } )
2016-08-19 15:35:41 -07:00
for i , v := range in {
2021-12-20 00:03:19 +01:00
c , vIsComment := v . ( Comment )
if vIsComment {
// If v is a comment, we add it to the slice of active comments.
// This allows us to also encrypt comments themselves by enabling encryption in a prior comment.
commentsStack [ len ( commentsStack ) - 1 ] = append ( commentsStack [ len ( commentsStack ) - 1 ] , c . Value )
}
newV , err := branch . walkValue ( v , path , commentsStack , onLeaves )
2016-08-19 15:35:41 -07:00
if err != nil {
return nil , err
}
in [ i ] = newV
2021-12-20 00:03:19 +01:00
if ! vIsComment {
// If v is not a comment, we clear the slice of active comments.
commentsStack [ len ( commentsStack ) - 1 ] = [ ] string { }
}
2016-08-19 15:35:41 -07:00
}
return in , nil
}
2021-12-20 00:03:19 +01:00
func ( branch TreeBranch ) walkBranch ( in TreeBranch , path [ ] string , commentsStack [ ] [ ] string , onLeaves func ( in interface { } , path [ ] string , commentsStack [ ] [ ] string ) ( interface { } , error ) ) ( TreeBranch , error ) {
// Because append returns a new slice, the original stack is not changed.
commentsStack = append ( commentsStack , [ ] string { } )
2016-08-19 15:35:41 -07:00
for i , item := range in {
2021-12-20 00:03:19 +01:00
if c , ok := item . Key . ( Comment ) ; ok {
// If key is a comment, we add it to the slice of active comments.
// This allows us to also encrypt comments themselves by enabling encryption in a prior comment.
commentsStack [ len ( commentsStack ) - 1 ] = append ( commentsStack [ len ( commentsStack ) - 1 ] , c . Value )
enc , err := branch . walkValue ( item . Key , path , commentsStack , onLeaves )
2017-08-15 13:48:55 -07:00
if err != nil {
return nil , err
}
if encComment , ok := enc . ( Comment ) ; ok {
in [ i ] . Key = encComment
continue
} else if comment , ok := enc . ( string ) ; ok {
in [ i ] . Key = Comment { Value : comment }
continue
} else {
return nil , fmt . Errorf ( "walkValue of Comment should be either Comment or string, was %T" , enc )
}
2016-10-20 14:48:38 -07:00
}
2021-12-20 00:03:19 +01:00
c , valueIsComment := item . Value . ( Comment )
if valueIsComment {
// If value is a comment, we add it to the slice of active comments.
// This allows us to also encrypt comments themselves by enabling encryption in a prior comment.
commentsStack [ len ( commentsStack ) - 1 ] = append ( commentsStack [ len ( commentsStack ) - 1 ] , c . Value )
}
2017-02-07 21:19:35 +01:00
key , ok := item . Key . ( string )
if ! ok {
2017-08-17 08:52:00 -07:00
return nil , fmt . Errorf ( "Tree contains a non-string key (type %T): %s. Only string keys are" +
2017-02-07 21:19:35 +01:00
"supported" , item . Key , item . Key )
}
2021-12-20 00:03:19 +01:00
newV , err := branch . walkValue ( item . Value , append ( path , key ) , commentsStack , onLeaves )
2016-08-19 15:35:41 -07:00
if err != nil {
return nil , err
}
in [ i ] . Value = newV
2021-12-20 00:03:19 +01:00
if ! valueIsComment {
// If value is not a comment, we clear the slice of active comments.
commentsStack [ len ( commentsStack ) - 1 ] = [ ] string { }
}
2016-08-19 15:35:41 -07:00
}
return in , nil
}
2021-12-20 00:03:19 +01:00
func ( tree Tree ) shouldBeEncrypted ( path [ ] string , commentsStack [ ] [ ] string , isComment bool ) bool {
encrypted := true
if tree . Metadata . UnencryptedSuffix != "" {
for _ , v := range path {
if strings . HasSuffix ( v , tree . Metadata . UnencryptedSuffix ) {
encrypted = false
break
}
}
}
if tree . Metadata . EncryptedSuffix != "" {
encrypted = false
for _ , v := range path {
if strings . HasSuffix ( v , tree . Metadata . EncryptedSuffix ) {
encrypted = true
break
}
}
}
if tree . Metadata . UnencryptedRegex != "" {
for _ , p := range path {
matched , _ := regexp . Match ( tree . Metadata . UnencryptedRegex , [ ] byte ( p ) )
if matched {
encrypted = false
break
}
}
}
if tree . Metadata . EncryptedRegex != "" {
encrypted = false
for _ , p := range path {
matched , _ := regexp . Match ( tree . Metadata . EncryptedRegex , [ ] byte ( p ) )
if matched {
encrypted = true
break
}
}
}
if tree . Metadata . UnencryptedCommentRegex != "" {
unencryptedComments :
for _ , cs := range commentsStack {
for _ , c := range cs {
matched , _ := regexp . Match ( tree . Metadata . UnencryptedCommentRegex , [ ] byte ( c ) )
if matched {
encrypted = false
break unencryptedComments
}
}
}
}
if tree . Metadata . EncryptedCommentRegex != "" {
lenCommentsStack := len ( commentsStack )
lenLastCommentsStack := len ( commentsStack [ lenCommentsStack - 1 ] )
encrypted = false
encryptedComments :
for i , cs := range commentsStack {
for j , c := range cs {
// A special case. We do not encrypt the comment line itself which matches the regex.
// So we skip the last line of the last set of comments. Only if the matches any previous
// line, we encrypt this comment. Otherwise we do not.
if isComment && i == lenCommentsStack - 1 && j == lenLastCommentsStack - 1 {
continue
}
matched , _ := regexp . Match ( tree . Metadata . EncryptedCommentRegex , [ ] byte ( c ) )
if matched {
encrypted = true
break encryptedComments
}
}
}
}
return encrypted
}
2018-09-20 11:18:34 -06:00
// Encrypt walks over the tree and encrypts all values with the provided cipher,
// except those whose key ends with the UnencryptedSuffix specified on the
2019-08-14 15:39:21 -04:00
// Metadata struct, those not ending with EncryptedSuffix, if EncryptedSuffix
2020-09-02 13:15:50 -04:00
// is provided (by default it is not), those not matching EncryptedRegex,
2021-12-20 00:03:19 +01:00
// if EncryptedRegex is provided (by default it is not), those matching UnencryptedRegex,
// if UnencryptedRegex is provided (by default it is not), those with their comment
// not matching EncryptedCommentRegex, if EncryptedCommentRegex is provided (by default
// it is not), or those with their comment matching UnencryptedCommentRegex, if
// UnencryptedCommentRegex is provided (by default it is not).
2021-12-18 21:50:57 +01:00
// If encryption is successful, it returns the MAC for the encrypted tree
// (all values if MACOnlyEncrypted is false, or only over values which end
// up encrypted if MACOnlyEncrypted is true).
2017-09-12 20:41:02 -07:00
func ( tree Tree ) Encrypt ( key [ ] byte , cipher Cipher ) ( string , error ) {
2018-04-22 12:21:58 -07:00
audit . SubmitEvent ( audit . EncryptEvent {
File : tree . FilePath ,
} )
2016-08-19 15:35:41 -07:00
hash := sha512 . New ( )
2021-12-18 21:50:57 +01:00
if tree . Metadata . MACOnlyEncrypted {
// We initialize with known set of bytes so that a MAC with this setting
// enabled is always different from a MAC with this setting disabled.
hash . Write ( MACOnlyEncryptedInitialization )
}
2018-09-20 11:18:34 -06:00
walk := func ( branch TreeBranch ) error {
2021-12-20 00:03:19 +01:00
_ , err := branch . walkBranch ( branch , make ( [ ] string , 0 ) , make ( [ ] [ ] string , 0 ) , func ( in interface { } , path [ ] string , commentsStack [ ] [ ] string ) ( interface { } , error ) {
_ , ok := in . ( Comment )
encrypted := tree . shouldBeEncrypted ( path , commentsStack , ok )
2021-12-18 21:50:57 +01:00
if ! tree . Metadata . MACOnlyEncrypted || encrypted {
// Only add to MAC if not a comment
2021-12-20 00:03:19 +01:00
if ! ok {
2021-12-18 21:50:57 +01:00
bytes , err := ToBytes ( in )
if err != nil {
return nil , fmt . Errorf ( "Could not convert %s to bytes: %s" , in , err )
}
hash . Write ( bytes )
}
}
2018-09-20 11:18:34 -06:00
if encrypted {
var err error
pathString := strings . Join ( path , ":" ) + ":"
in , err = cipher . Encrypt ( in , key , pathString )
if err != nil {
return nil , fmt . Errorf ( "Could not encrypt value: %s" , err )
}
2023-12-27 18:23:27 +01:00
if ok && tree . Metadata . UnencryptedCommentRegex != "" {
// If an encrypted comment matches tree.Metadata.UnencryptedCommentRegex, decryption will fail
// as the MAC does not match, and the commented value will not be decrypted.
matched , _ := regexp . Match ( tree . Metadata . UnencryptedCommentRegex , [ ] byte ( in . ( string ) ) )
if matched {
return nil , fmt . Errorf ( "Encrypted comment %q matches UnencryptedCommentRegex! Make sure that UnencryptedCommentRegex cannot match an encrypted comment." , in )
}
}
2016-08-22 14:04:31 -07:00
}
2018-09-20 11:18:34 -06:00
return in , nil
} )
return err
}
for _ , branch := range tree . Branches {
err := walk ( branch )
if err != nil {
return "" , fmt . Errorf ( "Error walking tree: %s" , err )
2016-08-22 14:04:31 -07:00
}
2016-08-19 15:35:41 -07:00
}
return fmt . Sprintf ( "%X" , hash . Sum ( nil ) ) , nil
}
2019-08-14 15:39:21 -04:00
// Decrypt walks over the tree and decrypts all values with the provided cipher,
// except those whose key ends with the UnencryptedSuffix specified on the Metadata struct,
// those not ending with EncryptedSuffix, if EncryptedSuffix is provided (by default it is not),
2020-09-02 13:15:50 -04:00
// those not matching EncryptedRegex, if EncryptedRegex is provided (by default it is not),
// or those matching UnencryptedRegex, if UnencryptedRegex is provided (by default it is not).
2021-12-18 21:50:57 +01:00
// If decryption is successful, it returns the MAC for the decrypted tree
// (all values if MACOnlyEncrypted is false, or only over values which end
// up decrypted if MACOnlyEncrypted is true).
2017-09-12 20:41:02 -07:00
func ( tree Tree ) Decrypt ( key [ ] byte , cipher Cipher ) ( string , error ) {
2017-09-07 10:49:27 -07:00
log . Debug ( "Decrypting tree" )
2018-04-22 12:21:58 -07:00
audit . SubmitEvent ( audit . DecryptEvent {
File : tree . FilePath ,
} )
2016-08-19 15:35:41 -07:00
hash := sha512 . New ( )
2021-12-18 21:50:57 +01:00
if tree . Metadata . MACOnlyEncrypted {
// We initialize with known set of bytes so that a MAC with this setting
// enabled is always different from a MAC with this setting disabled.
hash . Write ( MACOnlyEncryptedInitialization )
}
2018-09-20 11:18:34 -06:00
walk := func ( branch TreeBranch ) error {
2021-12-20 00:03:19 +01:00
_ , err := branch . walkBranch ( branch , make ( [ ] string , 0 ) , make ( [ ] [ ] string , 0 ) , func ( in interface { } , path [ ] string , commentsStack [ ] [ ] string ) ( interface { } , error ) {
c , ok := in . ( Comment )
encrypted := tree . shouldBeEncrypted ( path , commentsStack , ok )
2018-09-20 11:18:34 -06:00
var v interface { }
if encrypted {
var err error
pathString := strings . Join ( path , ":" ) + ":"
2021-12-20 00:03:19 +01:00
if ok {
2018-09-20 11:18:34 -06:00
v , err = cipher . Decrypt ( c . Value , key , pathString )
if err != nil {
// Assume the comment was not encrypted in the first place
log . WithField ( "comment" , c . Value ) .
Warn ( "Found possibly unencrypted comment in file. " +
"This is to be expected if the file being " +
"decrypted was created with an older version of " +
"SOPS." )
v = c
}
} else {
v , err = cipher . Decrypt ( in . ( string ) , key , pathString )
if err != nil {
return nil , fmt . Errorf ( "Could not decrypt value: %s" , err )
}
2017-08-15 13:48:55 -07:00
}
} else {
2018-09-20 11:18:34 -06:00
v = in
}
2021-12-18 21:50:57 +01:00
if ! tree . Metadata . MACOnlyEncrypted || encrypted {
// Only add to MAC if not a comment
if _ , ok := v . ( Comment ) ; ! ok {
bytes , err := ToBytes ( v )
if err != nil {
return nil , fmt . Errorf ( "Could not convert %s to bytes: %s" , in , err )
}
hash . Write ( bytes )
2017-08-15 13:48:55 -07:00
}
2016-08-22 14:04:31 -07:00
}
2018-09-20 11:18:34 -06:00
return v , nil
} )
return err
}
for _ , branch := range tree . Branches {
err := walk ( branch )
if err != nil {
return "" , fmt . Errorf ( "Error walking tree: %s" , err )
2016-08-19 15:35:41 -07:00
}
}
return fmt . Sprintf ( "%X" , hash . Sum ( nil ) ) , nil
}
2016-08-29 10:10:02 -07:00
// GenerateDataKey generates a new random data key and encrypts it with all MasterKeys.
2016-11-01 00:55:20 +01:00
func ( tree Tree ) GenerateDataKey ( ) ( [ ] byte , [ ] error ) {
2016-08-29 10:10:02 -07:00
newKey := make ( [ ] byte , 32 )
_ , err := rand . Read ( newKey )
if err != nil {
2016-11-01 00:55:20 +01:00
return nil , [ ] error { fmt . Errorf ( "Could not generate random key: %s" , err ) }
2016-08-29 10:10:02 -07:00
}
2016-11-01 00:55:20 +01:00
return newKey , tree . Metadata . UpdateMasterKeys ( newKey )
2016-08-29 10:10:02 -07:00
}
2017-09-12 09:59:23 -07:00
// GenerateDataKeyWithKeyServices generates a new random data key and encrypts it with all MasterKeys.
2017-08-23 11:19:24 -07:00
func ( tree * Tree ) GenerateDataKeyWithKeyServices ( svcs [ ] keyservice . KeyServiceClient ) ( [ ] byte , [ ] error ) {
2017-08-17 11:28:33 -07:00
newKey := make ( [ ] byte , 32 )
_ , err := rand . Read ( newKey )
if err != nil {
return nil , [ ] error { fmt . Errorf ( "Could not generate random key: %s" , err ) }
}
return newKey , tree . Metadata . UpdateMasterKeysWithKeyServices ( newKey , svcs )
}
2016-08-24 10:29:28 -07:00
// Metadata holds information about a file encrypted by sops
2016-08-11 11:44:00 -07:00
type Metadata struct {
LastModified time . Time
UnencryptedSuffix string
2018-04-08 12:43:43 +03:00
EncryptedSuffix string
2020-09-02 13:15:50 -04:00
UnencryptedRegex string
2019-08-14 15:39:21 -04:00
EncryptedRegex string
2021-12-20 00:03:19 +01:00
UnencryptedCommentRegex string
EncryptedCommentRegex string
2016-08-11 11:44:00 -07:00
MessageAuthenticationCode string
2021-12-18 21:50:57 +01:00
MACOnlyEncrypted bool
2016-08-11 11:44:00 -07:00
Version string
2017-08-23 11:06:47 -07:00
KeyGroups [ ] KeyGroup
2017-09-12 10:58:53 -07:00
// ShamirThreshold is the number of key groups required to recover the
2017-05-26 20:33:01 +02:00
// original data key
2017-09-12 10:58:53 -07:00
ShamirThreshold int
2017-08-23 11:19:24 -07:00
// DataKey caches the decrypted data key so it doesn't have to be decrypted with a master key every time it's needed
DataKey [ ] byte
2016-08-11 11:44:00 -07:00
}
2017-09-12 09:59:23 -07:00
// KeyGroup is a slice of SOPS MasterKeys that all encrypt the same part of the data key
2017-08-23 11:06:47 -07:00
type KeyGroup [ ] keys . MasterKey
2016-08-11 11:44:00 -07:00
2018-04-20 10:13:46 +02:00
// EncryptedFileLoader is the interface for loading of encrypted files. It provides a
// way to load encrypted SOPS files into the internal SOPS representation. Because it
// loads encrypted files, the returned data structure already contains all SOPS
// metadata.
type EncryptedFileLoader interface {
LoadEncryptedFile ( in [ ] byte ) ( Tree , error )
}
// PlainFileLoader is the interface for loading of plain text files. It provides a
// way to load unencrypted files into SOPS. Because the files it loads are
// unencrypted, the returned data structure does not contain any metadata.
type PlainFileLoader interface {
2018-09-20 11:18:34 -06:00
LoadPlainFile ( in [ ] byte ) ( TreeBranches , error )
2018-04-20 10:13:46 +02:00
}
// EncryptedFileEmitter is the interface for emitting encrypting files. It provides a
// way to emit encrypted files from the internal SOPS representation.
type EncryptedFileEmitter interface {
EmitEncryptedFile ( Tree ) ( [ ] byte , error )
}
// PlainFileEmitter is the interface for emitting plain text files. It provides a way
// to emit plain text files from the internal SOPS representation so that they can be
// shown
type PlainFileEmitter interface {
2018-09-20 11:18:34 -06:00
EmitPlainFile ( TreeBranches ) ( [ ] byte , error )
2018-04-20 10:13:46 +02:00
}
2019-07-08 15:32:33 -07:00
// ValueEmitter is the interface for emitting a value. It provides a way to emit
// values from the internal SOPS representation so that they can be shown
2018-04-20 10:13:46 +02:00
type ValueEmitter interface {
EmitValue ( interface { } ) ( [ ] byte , error )
}
2024-03-31 17:52:51 +01:00
// CheckEncrypted is the interface for testing whether a branch contains sops
2023-12-28 16:46:06 +01:00
// metadata. This is used to check whether a file is already encrypted or not.
2024-03-31 17:52:51 +01:00
type CheckEncrypted interface {
2023-12-28 16:46:06 +01:00
HasSopsTopLevelKey ( TreeBranch ) bool
}
2019-01-23 10:51:47 +01:00
// Store is used to interact with files, both encrypted and unencrypted.
2016-08-11 14:44:54 -07:00
type Store interface {
2018-04-20 10:13:46 +02:00
EncryptedFileLoader
PlainFileLoader
EncryptedFileEmitter
PlainFileEmitter
ValueEmitter
2024-03-31 17:52:51 +01:00
CheckEncrypted
2016-08-11 14:44:54 -07:00
}
2016-08-24 10:29:28 -07:00
// MasterKeyCount returns the number of master keys available
2016-08-11 11:44:00 -07:00
func ( m * Metadata ) MasterKeyCount ( ) int {
count := 0
2017-08-23 11:06:47 -07:00
for _ , group := range m . KeyGroups {
count += len ( group )
2016-08-11 11:44:00 -07:00
}
return count
}
2016-08-24 10:29:28 -07:00
2017-09-12 09:59:23 -07:00
// UpdateMasterKeysWithKeyServices encrypts the data key with all master keys using the provided key services
2017-08-17 11:28:33 -07:00
func ( m * Metadata ) UpdateMasterKeysWithKeyServices ( dataKey [ ] byte , svcs [ ] keyservice . KeyServiceClient ) ( errs [ ] error ) {
2017-08-17 09:36:22 -07:00
if len ( svcs ) == 0 {
return [ ] error {
2017-09-12 09:59:23 -07:00
fmt . Errorf ( "no key services provided, cannot update master keys" ) ,
2017-08-17 09:36:22 -07:00
}
}
2017-08-23 11:06:47 -07:00
var parts [ ] [ ] byte
if len ( m . KeyGroups ) == 1 {
// If there's only one key group, we can't do Shamir. All keys
// in the group encrypt the whole data key.
parts = append ( parts , dataKey )
} else {
var err error
2017-09-12 10:58:53 -07:00
if m . ShamirThreshold == 0 {
m . ShamirThreshold = len ( m . KeyGroups )
2016-08-11 11:44:00 -07:00
}
2017-09-07 10:49:27 -07:00
log . WithFields ( logrus . Fields {
2017-09-16 17:52:14 -07:00
"quorum" : m . ShamirThreshold ,
2017-09-07 10:49:27 -07:00
"parts" : len ( m . KeyGroups ) ,
} ) . Info ( "Splitting data key with Shamir Secret Sharing" )
2017-09-16 17:52:14 -07:00
parts , err = shamir . Split ( dataKey , len ( m . KeyGroups ) , int ( m . ShamirThreshold ) )
2017-08-23 11:06:47 -07:00
if err != nil {
2017-09-12 09:59:23 -07:00
errs = append ( errs , fmt . Errorf ( "could not split data key into parts for Shamir: %s" , err ) )
2017-08-23 11:06:47 -07:00
return
}
if len ( parts ) != len ( m . KeyGroups ) {
2017-09-12 09:59:23 -07:00
errs = append ( errs , fmt . Errorf ( "not enough parts obtained from Shamir: need %d, got %d" , len ( m . KeyGroups ) , len ( parts ) ) )
2017-08-23 11:06:47 -07:00
return
2017-05-25 15:31:12 +02:00
}
}
2017-08-23 11:06:47 -07:00
for i , group := range m . KeyGroups {
part := parts [ i ]
for _ , key := range group {
2017-08-22 15:39:24 -07:00
svcKey := keyservice . KeyFromMasterKey ( key )
2017-08-24 17:39:19 -07:00
var keyErrs [ ] error
encrypted := false
2017-08-22 15:39:24 -07:00
for _ , svc := range svcs {
rsp , err := svc . Encrypt ( context . Background ( ) , & keyservice . EncryptRequest {
Key : & svcKey ,
2017-08-23 11:06:47 -07:00
Plaintext : part ,
2017-08-22 15:39:24 -07:00
} )
if err != nil {
2021-02-17 21:21:20 +00:00
keyErrs = append ( keyErrs , fmt . Errorf ( "failed to encrypt new data key with master key %q: %w" , key . ToString ( ) , err ) )
2017-08-22 15:39:24 -07:00
continue
}
key . SetEncryptedDataKey ( rsp . Ciphertext )
2017-08-24 17:39:19 -07:00
encrypted = true
2017-08-22 15:39:24 -07:00
// Only need to encrypt the key successfully with one service
break
2017-05-25 15:31:12 +02:00
}
2017-08-24 17:39:19 -07:00
if ! encrypted {
errs = append ( errs , keyErrs ... )
}
2017-05-25 15:31:12 +02:00
}
}
2017-08-23 11:19:24 -07:00
m . DataKey = dataKey
2017-05-25 15:31:12 +02:00
return
}
2017-08-17 09:36:22 -07:00
// UpdateMasterKeys encrypts the data key with all master keys
func ( m * Metadata ) UpdateMasterKeys ( dataKey [ ] byte ) ( errs [ ] error ) {
2017-08-17 11:28:33 -07:00
return m . UpdateMasterKeysWithKeyServices ( dataKey , [ ] keyservice . KeyServiceClient {
keyservice . NewLocalClient ( ) ,
2017-08-17 09:36:22 -07:00
} )
}
2017-08-23 11:06:47 -07:00
// GetDataKeyWithKeyServices retrieves the data key, asking KeyServices to decrypt it with each
// MasterKey in the Metadata's KeySources until one of them succeeds.
2023-11-07 19:28:34 +02:00
func ( m Metadata ) GetDataKeyWithKeyServices ( svcs [ ] keyservice . KeyServiceClient , decryptionOrder [ ] string ) ( [ ] byte , error ) {
2017-08-23 11:19:24 -07:00
if m . DataKey != nil {
return m . DataKey , nil
}
2017-10-06 11:55:12 -07:00
getDataKeyErr := getDataKeyError {
RequiredSuccessfulKeyGroups : m . ShamirThreshold ,
GroupResults : make ( [ ] error , len ( m . KeyGroups ) ) ,
}
2017-08-23 11:06:47 -07:00
var parts [ ] [ ] byte
2017-10-06 11:55:12 -07:00
for i , group := range m . KeyGroups {
2023-11-07 19:28:34 +02:00
part , err := decryptKeyGroup ( group , svcs , decryptionOrder )
2017-10-06 11:55:12 -07:00
if err == nil {
parts = append ( parts , part )
2016-08-29 10:10:02 -07:00
}
2017-10-06 11:55:12 -07:00
getDataKeyErr . GroupResults [ i ] = err
2016-08-29 10:10:02 -07:00
}
2017-08-23 11:06:47 -07:00
var dataKey [ ] byte
if len ( m . KeyGroups ) > 1 {
2017-09-12 10:58:53 -07:00
if len ( parts ) < m . ShamirThreshold {
2017-10-06 11:55:12 -07:00
return nil , & getDataKeyErr
2017-08-23 11:06:47 -07:00
}
var err error
dataKey , err = shamir . Combine ( parts )
if err != nil {
2017-09-12 09:59:23 -07:00
return nil , fmt . Errorf ( "could not get data key from shamir parts: %s" , err )
2017-08-23 11:06:47 -07:00
}
} else {
if len ( parts ) != 1 {
2017-10-06 11:55:12 -07:00
return nil , & getDataKeyErr
2017-08-23 11:06:47 -07:00
}
dataKey = parts [ 0 ]
}
2017-09-07 10:49:27 -07:00
log . Info ( "Data key recovered successfully" )
2017-08-23 11:19:24 -07:00
m . DataKey = dataKey
2017-08-23 11:06:47 -07:00
return dataKey , nil
2017-08-17 09:36:22 -07:00
}
2017-10-06 11:55:12 -07:00
// decryptKeyGroup tries to decrypt the contents of the provided KeyGroup with
// any of the MasterKeys in the KeyGroup with any of the provided key services,
// returning as soon as one key service succeeds.
2023-11-07 19:28:34 +02:00
func decryptKeyGroup ( group KeyGroup , svcs [ ] keyservice . KeyServiceClient , decryptionOrder [ ] string ) ( [ ] byte , error ) {
2017-10-06 11:55:12 -07:00
var keyErrs [ ] error
2023-11-07 19:28:34 +02:00
// Sort MasterKeys in the group so we try them in specific order
// Use sorted indices to avoid group slice modification
indices := sortKeyGroupIndices ( group , decryptionOrder )
for _ , indexVal := range indices {
key := group [ indexVal ]
2017-10-06 11:55:12 -07:00
part , err := decryptKey ( key , svcs )
if err != nil {
keyErrs = append ( keyErrs , err )
} else {
return part , nil
}
}
return nil , decryptKeyErrors ( keyErrs )
}
2023-11-07 19:28:34 +02:00
// sortKeyGroupIndices returns indices that would sort the KeyGroup
// according to decryptionOrder
func sortKeyGroupIndices ( group KeyGroup , decryptionOrder [ ] string ) [ ] int {
priorities := make ( map [ string ] int )
// give ordered weights
for i , v := range decryptionOrder {
priorities [ v ] = i
}
maxPriority := len ( decryptionOrder )
// initialize indices
n := len ( group )
indices := make ( [ ] int , n )
for i := 0 ; i < n ; i ++ {
indices [ i ] = i
}
sort . SliceStable ( indices , func ( i , j int ) bool {
keyTypeI := group [ indices [ i ] ] . TypeToIdentifier ( )
keyTypeJ := group [ indices [ j ] ] . TypeToIdentifier ( )
priorityI , ok := priorities [ keyTypeI ]
if ! ok {
priorityI = maxPriority
}
priorityJ , ok := priorities [ keyTypeJ ]
if ! ok {
priorityJ = maxPriority
}
return priorityI < priorityJ
} )
return indices
}
2017-10-06 11:55:12 -07:00
// decryptKey tries to decrypt the contents of the provided MasterKey with any
// of the key services, returning as soon as one key service succeeds.
func decryptKey ( key keys . MasterKey , svcs [ ] keyservice . KeyServiceClient ) ( [ ] byte , error ) {
svcKey := keyservice . KeyFromMasterKey ( key )
2019-07-08 15:32:33 -07:00
var part [ ] byte
2017-10-06 11:55:12 -07:00
decryptErr := decryptKeyError {
keyName : key . ToString ( ) ,
}
for _ , svc := range svcs {
// All keys in a key group encrypt the same part, so as soon
// as we decrypt it successfully with one key, we need to
// proceed with the next group
var err error
if part == nil {
var rsp * keyservice . DecryptResponse
rsp , err = svc . Decrypt (
context . Background ( ) ,
& keyservice . DecryptRequest {
Ciphertext : key . EncryptedDataKey ( ) ,
Key : & svcKey ,
} )
if err == nil {
part = rsp . Plaintext
}
}
decryptErr . errs = append ( decryptErr . errs , err )
}
if part != nil {
return part , nil
}
return nil , & decryptErr
}
2017-08-17 09:36:22 -07:00
// GetDataKey retrieves the data key from the first MasterKey in the Metadata's KeySources that's able to return it,
// using the local KeyService
func ( m Metadata ) GetDataKey ( ) ( [ ] byte , error ) {
2017-08-17 11:28:33 -07:00
return m . GetDataKeyWithKeyServices ( [ ] keyservice . KeyServiceClient {
keyservice . NewLocalClient ( ) ,
2023-11-07 19:28:34 +02:00
} , nil )
2016-08-29 10:10:02 -07:00
}
2016-08-26 12:01:06 -07:00
// ToBytes converts a string, int, float or bool to a byte representation.
func ToBytes ( in interface { } ) ( [ ] byte , error ) {
2016-08-19 15:35:41 -07:00
switch in := in . ( type ) {
case string :
return [ ] byte ( in ) , nil
case int :
return [ ] byte ( strconv . Itoa ( in ) ) , nil
case float64 :
return [ ] byte ( strconv . FormatFloat ( in , 'f' , - 1 , 64 ) ) , nil
case bool :
2023-08-17 01:21:25 +02:00
boolB := [ ] byte ( "True" )
if ! in {
boolB = [ ] byte ( "False" )
}
return boolB , nil
2016-08-19 15:35:41 -07:00
case [ ] byte :
return in , nil
2017-08-15 13:48:55 -07:00
case Comment :
return ToBytes ( in . Value )
2016-08-19 15:35:41 -07:00
default :
return nil , fmt . Errorf ( "Could not convert unknown type %T to bytes" , in )
}
}
2019-07-16 14:33:59 -07:00
// EmitAsMap will emit the tree branches as a map. This is used by the publish
// command for writing decrypted trees to various destinations. Should only be
// used for outputting to data structures in code.
func EmitAsMap ( in TreeBranches ) ( map [ string ] interface { } , error ) {
data := map [ string ] interface { } { }
for _ , branch := range in {
for _ , item := range branch {
if _ , ok := item . Key . ( Comment ) ; ok {
continue
}
val , err := encodeValueForMap ( item . Value )
if err != nil {
return nil , err
}
data [ item . Key . ( string ) ] = val
}
}
return data , nil
}
func encodeValueForMap ( v interface { } ) ( interface { } , error ) {
switch v := v . ( type ) {
case TreeBranch :
return EmitAsMap ( [ ] TreeBranch { v } )
default :
return v , nil
}
}