github.com/trustbloc/kms-go@v1.1.2/secretlock/local/local_secret_lock.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 SPDX-License-Identifier: Apache-2.0 4 */ 5 6 package local 7 8 import ( 9 "crypto/cipher" 10 "encoding/base64" 11 "errors" 12 "fmt" 13 "io" 14 "log" 15 "os" 16 17 "github.com/google/tink/go/subtle/random" 18 19 "github.com/trustbloc/kms-go/spi/secretlock" 20 21 cipherutil "github.com/trustbloc/kms-go/secretlock/local/internal/cipher" 22 ) 23 24 // package local provides a local secret lock service. The user must create a master key and store it 25 // in a local file or an environment variable prior to using this service. 26 // 27 // The user has the option to encrypt the master key using hkdf.NewMasterLock(passphrase, hash func(), salt) 28 // found in the sub package masterlock/hkdf. There's also the option of using pbkdf2.NewMasterLock() instead of hkdf 29 // which is located under masterlock/pbkdf2. 30 // 31 // This lock services uses the NIST approved AES-GCM 256 bit encryption as per NIST SP 800-38D. 32 // 33 // The user can then call either: 34 // MasterKeyFromPath(path) or 35 // MasterKeyFromEnv(envPrefix, keyURI) 36 // to get an io.Reader instance needed to read the master key and create a keys Lock service. 37 // 38 // The content of the master key reader may be either raw bytes or base64URL encoded (by masterlock if protected or 39 // manually if not). Base64URL encoding is useful when setting a master key in an environment variable as some OSs may 40 // reject setting env variables with binary data as value. The service will attempt to base64URL decode the content of 41 // reader first and if it fails, will try to create the service with the raw (binary) content. 42 // 43 // To get the lock service, call: 44 // NewService(reader, secLock) 45 // where reader is the instance returned from one of the MasterKeyFrom..() functions mentioned above 46 // and secLock which is the masterKey lock used to encrypt/decrypt the master key. If secLock is nil 47 // then the masterKey content in reader will be used as-is without being decrypted. The keys however are always 48 // encrypted using the read masterKey. 49 50 var logger = log.New(os.Stderr, " [kms-go/lock] ", log.Ldate|log.Ltime|log.LUTC) 51 52 const masterKeyLen = 512 53 54 // Lock is a secret lock service responsible for encrypting keys using a master key. 55 type Lock struct { 56 aead cipher.AEAD 57 } 58 59 // NewService creates a new instance of local secret lock service using a master key in masterKeyReader. 60 // If the masterKey is not protected (secLock=nil) this function will attempt to base64 URL Decode the 61 // content of masterKeyReader and if it fails, then will attempt to create a secret lock cipher with the raw key as is. 62 func NewService(masterKeyReader io.Reader, secLock secretlock.Service) (secretlock.Service, error) { 63 masterKeyData := make([]byte, masterKeyLen) 64 65 if masterKeyReader == nil { 66 return nil, fmt.Errorf("masterKeyReader is nil") 67 } 68 69 n, err := masterKeyReader.Read(masterKeyData) 70 if err != nil { 71 if !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) { 72 return nil, err 73 } 74 } 75 76 if n == 0 { 77 return nil, fmt.Errorf("masterKeyReader is empty") 78 } 79 80 var masterKey []byte 81 82 // if secLock not empty, then masterKeyData is encrypted (protected), let's decrypt it first. 83 if secLock != nil { 84 decResponse, e := secLock.Decrypt("", &secretlock.DecryptRequest{ 85 Ciphertext: string(masterKeyData[:n]), 86 }) 87 if e != nil { 88 return nil, e 89 } 90 91 masterKey = []byte(decResponse.Plaintext) 92 } else { 93 // masterKeyData is not encrypted, base64URL decode it 94 masterKey, err = base64.URLEncoding.DecodeString(string(masterKeyData[:n])) 95 if err != nil { 96 // attempt to create a service using the key content from reader as is 97 98 masterKey = make([]byte, n) 99 100 // copy masterKey read from reader directly 101 copy(masterKey, masterKeyData) 102 } 103 } 104 105 // finally create the cipher to be used by the lock service 106 aead, err := cipherutil.CreateAESCipher(masterKey) 107 if err != nil { 108 return nil, err 109 } 110 111 return &Lock{aead: aead}, nil 112 } 113 114 // Encrypt a key in req using master key in the local secret lock service 115 // (keyURI is used for remote locks, it is ignored by this implementation). 116 func (s *Lock) Encrypt(keyURI string, req *secretlock.EncryptRequest) (*secretlock.EncryptResponse, error) { 117 nonce := random.GetRandomBytes(uint32(s.aead.NonceSize())) 118 ct := s.aead.Seal(nil, nonce, []byte(req.Plaintext), []byte(req.AdditionalAuthenticatedData)) 119 ct = append(nonce, ct...) 120 121 return &secretlock.EncryptResponse{ 122 Ciphertext: base64.URLEncoding.EncodeToString(ct), 123 }, nil 124 } 125 126 // Decrypt a key in req using master key in the local secret lock service 127 // (keyURI is used for remote locks, it is ignored by this implementation). 128 func (s *Lock) Decrypt(keyURI string, req *secretlock.DecryptRequest) (*secretlock.DecryptResponse, error) { 129 ct, err := base64.URLEncoding.DecodeString(req.Ciphertext) 130 if err != nil { 131 return nil, err 132 } 133 134 nonceSize := uint32(s.aead.NonceSize()) 135 136 // ensure ciphertext contains more than nonce+ciphertext (result from Encrypt()) 137 if len(ct) <= int(nonceSize) { 138 return nil, fmt.Errorf("invalid request") 139 } 140 141 nonce := ct[0:nonceSize] 142 ct = ct[nonceSize:] 143 144 pt, err := s.aead.Open(nil, nonce, ct, []byte(req.AdditionalAuthenticatedData)) 145 if err != nil { 146 return nil, err 147 } 148 149 return &secretlock.DecryptResponse{Plaintext: string(pt)}, nil 150 }