@@ -8,13 +8,12 @@ import (
88 "encoding/base64"
99 "encoding/hex"
1010 "encoding/json"
11+ "errors"
1112 "fmt"
1213 "io"
1314 "net/http"
1415 "time"
1516
16- "github.com/pkg/errors"
17-
1817 "golang.org/x/crypto/bcrypt"
1918)
2019
@@ -53,35 +52,33 @@ func (k *ApiKey) Hash() error {
5352 return err
5453}
5554
56- // IsCorrect returns true if and only if the given string is a match for HashedSecret
57- func (k * ApiKey ) IsCorrect (given string ) (bool , error ) {
55+ // IsCorrect returns true if and only if the key is active and the given string is a match for HashedSecret
56+ func (k * ApiKey ) IsCorrect (given string ) error {
57+ if k .ActivatedAt == 0 {
58+ return fmt .Errorf ("key is not active: %s" , k .Key )
59+ }
60+
5861 if given == "" {
59- return false , errors .New ("secret to compare cannot be empty" )
62+ return errors .New ("secret to compare cannot be empty" )
6063 }
64+
6165 if k .HashedSecret == "" {
62- return false , errors .New ("cannot compare with empty hashed secret" )
66+ return errors .New ("cannot compare with empty hashed secret" )
6367 }
6468
6569 err := bcrypt .CompareHashAndPassword ([]byte (k .HashedSecret ), []byte (given ))
6670 if err != nil {
67- return false , err
71+ return err
6872 }
6973
70- return true , nil
74+ return nil
7175}
7276
7377// EncryptData uses the Secret to AES encrypt an arbitrary data block. It does not encrypt the key itself.
7478func (k * ApiKey ) EncryptData (plaintext []byte ) ([]byte , error ) {
75- var sec []byte
76- var err error
77- sec , err = base64 .StdEncoding .DecodeString (k .Secret )
78- if err != nil {
79- sec = []byte (k .Secret )
80- }
81- // create cipher block with api secret as aes key
82- block , err := aes .NewCipher (sec )
79+ block , err := newCipherBlock (k .Secret )
8380 if err != nil {
84- return [] byte {} , err
81+ return nil , err
8582 }
8683
8784 // byte array to hold encrypted content
@@ -103,16 +100,9 @@ func (k *ApiKey) EncryptData(plaintext []byte) ([]byte, error) {
103100
104101// DecryptData uses the Secret to AES decrypt an arbitrary data block. It does not decrypt the key itself.
105102func (k * ApiKey ) DecryptData (ciphertext []byte ) ([]byte , error ) {
106- var sec []byte
107- var err error
108- sec , err = base64 .StdEncoding .DecodeString (k .Secret )
109- if err != nil {
110- sec = []byte (k .Secret )
111- }
112-
113- block , err := aes .NewCipher (sec )
103+ block , err := newCipherBlock (k .Secret )
114104 if err != nil {
115- return [] byte {}, errors . Wrap ( err , "failed to create new cipher" )
105+ return nil , err
116106 }
117107
118108 // plaintext must be as long as ciphertext minus the length of the IV, which is the same as the AES block size
@@ -128,19 +118,34 @@ func (k *ApiKey) DecryptData(ciphertext []byte) ([]byte, error) {
128118 return plaintext , nil
129119}
130120
131- // DecryptLegacy uses the Secret to AES decrypt an arbitrary data block. This is intended only for legacy data such
132- // as U2F keys.
133- func (k * ApiKey ) DecryptLegacy (ciphertext []byte ) ([]byte , error ) {
134- var sec []byte
135- var err error
136- sec , err = base64 .StdEncoding .DecodeString (k .Secret )
121+ // EncryptLegacy uses the Secret to AES encrypt an arbitrary data block. This is intended only for legacy data such
122+ // as U2F keys. The returned data is the Base64-encoded IV and the Base64-encoded cipher text separated by a colon.
123+ func (k * ApiKey ) EncryptLegacy (plaintext []byte ) ([]byte , error ) {
124+ block , err := newCipherBlock (k .Secret )
137125 if err != nil {
138- sec = [] byte ( k . Secret )
126+ return nil , err
139127 }
140128
141- block , err := aes .NewCipher (sec )
129+ iv := make ([]byte , aes .BlockSize )
130+ if _ , err = io .ReadFull (rand .Reader , iv ); err != nil {
131+ return nil , fmt .Errorf ("failed to create random data for initialization vector: %w" , err )
132+ }
133+
134+ ciphertext := make ([]byte , len (plaintext ))
135+ stream := cipher .NewCTR (block , iv )
136+ stream .XORKeyStream (ciphertext , plaintext )
137+
138+ ivBase64 := base64 .StdEncoding .EncodeToString (iv )
139+ cipherBase64 := base64 .StdEncoding .EncodeToString (ciphertext )
140+ return []byte (ivBase64 + ":" + cipherBase64 ), nil
141+ }
142+
143+ // DecryptLegacy uses the Secret to AES decrypt an arbitrary data block. This is intended only for legacy data such
144+ // as U2F keys.
145+ func (k * ApiKey ) DecryptLegacy (ciphertext []byte ) ([]byte , error ) {
146+ block , err := newCipherBlock (k .Secret )
142147 if err != nil {
143- return [] byte {}, errors . Wrap ( err , "failed to create new cipher" )
148+ return nil , err
144149 }
145150
146151 // data was encrypted, then base64 encoded, then joined with a :, need to split
@@ -302,3 +307,20 @@ func NewApiKey(email string) (ApiKey, error) {
302307 }
303308 return key , nil
304309}
310+
311+ // newCipherBlock creates a new cipher.Block from a base64-encoded AES key. If the string is not valid base64 data, it
312+ // will be interpreted as binary data.
313+ func newCipherBlock (key string ) (cipher.Block , error ) {
314+ var sec []byte
315+ var err error
316+ sec , err = base64 .StdEncoding .DecodeString (key )
317+ if err != nil {
318+ sec = []byte (key )
319+ }
320+
321+ block , err := aes .NewCipher (sec )
322+ if err != nil {
323+ return nil , fmt .Errorf ("failed to create new cipher: %w" , err )
324+ }
325+ return block , nil
326+ }
0 commit comments