secret.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package vfs
  2. import (
  3. "crypto/aes"
  4. "crypto/cipher"
  5. "crypto/rand"
  6. "crypto/sha256"
  7. "encoding/hex"
  8. "errors"
  9. "io"
  10. "github.com/drakkan/sftpgo/utils"
  11. )
  12. // SecretStatus defines the statuses of a Secret object
  13. type SecretStatus = string
  14. const (
  15. // SecretStatusPlain means the secret is in plain text and must be encrypted
  16. SecretStatusPlain SecretStatus = "Plain"
  17. // SecretStatusAES256GCM means the secret is encrypted using AES-256-GCM
  18. SecretStatusAES256GCM SecretStatus = "AES-256-GCM"
  19. // SecretStatusRedacted means the secret is redacted
  20. SecretStatusRedacted SecretStatus = "Redacted"
  21. )
  22. var (
  23. errWrongSecretStatus = errors.New("wrong secret status")
  24. errMalformedCiphertext = errors.New("malformed ciphertext")
  25. errInvalidSecret = errors.New("invalid secret")
  26. validSecretStatuses = []string{SecretStatusPlain, SecretStatusAES256GCM, SecretStatusRedacted}
  27. )
  28. // Secret defines the struct used to store confidential data
  29. type Secret struct {
  30. Status SecretStatus `json:"status,omitempty"`
  31. Payload string `json:"payload,omitempty"`
  32. Key string `json:"key,omitempty"`
  33. AdditionalData string `json:"additional_data,omitempty"`
  34. }
  35. // GetSecretFromCompatString returns a secret from the previous format
  36. func GetSecretFromCompatString(secret string) (Secret, error) {
  37. s := Secret{}
  38. plain, err := utils.DecryptData(secret)
  39. if err != nil {
  40. return s, errMalformedCiphertext
  41. }
  42. s.Status = SecretStatusPlain
  43. s.Payload = plain
  44. return s, nil
  45. }
  46. // IsEncrypted returns true if the secret is encrypted
  47. // This isn't a pointer receiver because we don't want to pass
  48. // a pointer to html template
  49. func (s *Secret) IsEncrypted() bool {
  50. return s.Status == SecretStatusAES256GCM
  51. }
  52. // IsPlain returns true if the secret is in plain text
  53. func (s *Secret) IsPlain() bool {
  54. return s.Status == SecretStatusPlain
  55. }
  56. // IsRedacted returns true if the secret is redacted
  57. func (s *Secret) IsRedacted() bool {
  58. return s.Status == SecretStatusRedacted
  59. }
  60. // IsEmpty returns true if all fields are empty
  61. func (s *Secret) IsEmpty() bool {
  62. if s.Status != "" {
  63. return false
  64. }
  65. if s.Payload != "" {
  66. return false
  67. }
  68. if s.Key != "" {
  69. return false
  70. }
  71. if s.AdditionalData != "" {
  72. return false
  73. }
  74. return true
  75. }
  76. // IsValid returns true if the secret is not empty and valid
  77. func (s *Secret) IsValid() bool {
  78. if !s.IsValidInput() {
  79. return false
  80. }
  81. if s.Status == SecretStatusAES256GCM {
  82. if len(s.Key) != 64 {
  83. return false
  84. }
  85. }
  86. return true
  87. }
  88. // IsValidInput returns true if the secret is a valid user input
  89. func (s *Secret) IsValidInput() bool {
  90. if !utils.IsStringInSlice(s.Status, validSecretStatuses) {
  91. return false
  92. }
  93. if s.Payload == "" {
  94. return false
  95. }
  96. return true
  97. }
  98. // Hide hides info to decrypt data
  99. func (s *Secret) Hide() {
  100. s.Key = ""
  101. s.AdditionalData = ""
  102. }
  103. // deriveKey is a weak method of deriving a key but it is still better than using the key as it is.
  104. // We should use a KMS in future
  105. func (s *Secret) deriveKey(key []byte) []byte {
  106. var combined []byte
  107. combined = append(combined, key...)
  108. if s.AdditionalData != "" {
  109. combined = append(combined, []byte(s.AdditionalData)...)
  110. }
  111. combined = append(combined, key...)
  112. hash := sha256.Sum256(combined)
  113. return hash[:]
  114. }
  115. // Encrypt encrypts a plain text Secret object
  116. func (s *Secret) Encrypt() error {
  117. if s.Payload == "" {
  118. return errInvalidSecret
  119. }
  120. switch s.Status {
  121. case SecretStatusPlain:
  122. key := make([]byte, 32)
  123. if _, err := io.ReadFull(rand.Reader, key); err != nil {
  124. return err
  125. }
  126. block, err := aes.NewCipher(s.deriveKey(key))
  127. if err != nil {
  128. return err
  129. }
  130. gcm, err := cipher.NewGCM(block)
  131. if err != nil {
  132. return err
  133. }
  134. nonce := make([]byte, gcm.NonceSize())
  135. if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
  136. return err
  137. }
  138. var aad []byte
  139. if s.AdditionalData != "" {
  140. aad = []byte(s.AdditionalData)
  141. }
  142. ciphertext := gcm.Seal(nonce, nonce, []byte(s.Payload), aad)
  143. s.Key = hex.EncodeToString(key)
  144. s.Payload = hex.EncodeToString(ciphertext)
  145. s.Status = SecretStatusAES256GCM
  146. return nil
  147. default:
  148. return errWrongSecretStatus
  149. }
  150. }
  151. // Decrypt decrypts a Secret object
  152. func (s *Secret) Decrypt() error {
  153. switch s.Status {
  154. case SecretStatusAES256GCM:
  155. encrypted, err := hex.DecodeString(s.Payload)
  156. if err != nil {
  157. return err
  158. }
  159. key, err := hex.DecodeString(s.Key)
  160. if err != nil {
  161. return err
  162. }
  163. block, err := aes.NewCipher(s.deriveKey(key))
  164. if err != nil {
  165. return err
  166. }
  167. gcm, err := cipher.NewGCM(block)
  168. if err != nil {
  169. return err
  170. }
  171. nonceSize := gcm.NonceSize()
  172. if len(encrypted) < nonceSize {
  173. return errMalformedCiphertext
  174. }
  175. nonce, ciphertext := encrypted[:nonceSize], encrypted[nonceSize:]
  176. var aad []byte
  177. if s.AdditionalData != "" {
  178. aad = []byte(s.AdditionalData)
  179. }
  180. plaintext, err := gcm.Open(nil, nonce, ciphertext, aad)
  181. if err != nil {
  182. return err
  183. }
  184. s.Status = SecretStatusPlain
  185. s.Payload = string(plaintext)
  186. s.Key = ""
  187. s.AdditionalData = ""
  188. return nil
  189. default:
  190. return errWrongSecretStatus
  191. }
  192. }