mirror of https://github.com/mautrix/go.git
Fix base64 in SSSS keys (#159)
parent
3756213bae
commit
38b67b622d
|
@ -67,12 +67,12 @@ func NewKey(passphrase string) (*Key, error) {
|
|||
if _, err := rand.Read(ivBytes[:]); err != nil {
|
||||
return nil, fmt.Errorf("failed to get random bytes for IV: %w", err)
|
||||
}
|
||||
keyData.IV = base64.StdEncoding.EncodeToString(ivBytes[:])
|
||||
keyData.IV = base64.RawStdEncoding.EncodeToString(ivBytes[:])
|
||||
keyData.MAC = keyData.calculateHash(ssssKey)
|
||||
|
||||
return &Key{
|
||||
Key: ssssKey,
|
||||
ID: base64.StdEncoding.EncodeToString(keyIDBytes),
|
||||
ID: base64.RawStdEncoding.EncodeToString(keyIDBytes),
|
||||
Metadata: &keyData,
|
||||
}, nil
|
||||
}
|
||||
|
@ -87,13 +87,18 @@ func (key *Key) Encrypt(eventType string, data []byte) EncryptedKeyData {
|
|||
aesKey, hmacKey := utils.DeriveKeysSHA256(key.Key, eventType)
|
||||
|
||||
iv := utils.GenA256CTRIV()
|
||||
payload := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
|
||||
base64.StdEncoding.Encode(payload, data)
|
||||
// For some reason, keys in secret storage are base64 encoded before encrypting.
|
||||
// Even more confusingly, it's a part of each key type's spec rather than the secrets spec.
|
||||
// Key backup (`m.megolm_backup.v1`): https://spec.matrix.org/v1.9/client-server-api/#recovery-key
|
||||
// Cross-signing (master, etc): https://spec.matrix.org/v1.9/client-server-api/#cross-signing (the very last paragraph)
|
||||
// It's also not clear whether unpadded base64 is the right option, but assuming it is because everything else is unpadded.
|
||||
payload := make([]byte, base64.RawStdEncoding.EncodedLen(len(data)))
|
||||
base64.RawStdEncoding.Encode(payload, data)
|
||||
utils.XorA256CTR(payload, aesKey, iv)
|
||||
|
||||
return EncryptedKeyData{
|
||||
Ciphertext: base64.StdEncoding.EncodeToString(payload),
|
||||
IV: base64.StdEncoding.EncodeToString(iv[:]),
|
||||
Ciphertext: base64.RawStdEncoding.EncodeToString(payload),
|
||||
IV: base64.RawStdEncoding.EncodeToString(iv[:]),
|
||||
MAC: utils.HMACSHA256B64(payload, hmacKey),
|
||||
}
|
||||
}
|
||||
|
@ -101,10 +106,10 @@ func (key *Key) Encrypt(eventType string, data []byte) EncryptedKeyData {
|
|||
// Decrypt decrypts the given encrypted data with this key.
|
||||
func (key *Key) Decrypt(eventType string, data EncryptedKeyData) ([]byte, error) {
|
||||
var ivBytes [utils.AESCTRIVLength]byte
|
||||
decodedIV, _ := base64.StdEncoding.DecodeString(data.IV)
|
||||
decodedIV, _ := base64.RawStdEncoding.DecodeString(strings.TrimRight(data.IV, "="))
|
||||
copy(ivBytes[:], decodedIV)
|
||||
|
||||
payload, err := base64.StdEncoding.DecodeString(data.Ciphertext)
|
||||
payload, err := base64.RawStdEncoding.DecodeString(strings.TrimRight(data.Ciphertext, "="))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -114,11 +119,11 @@ func (key *Key) Decrypt(eventType string, data EncryptedKeyData) ([]byte, error)
|
|||
|
||||
// compare the stored MAC with the one we calculated from the ciphertext
|
||||
calcMac := utils.HMACSHA256B64(payload, hmacKey)
|
||||
if strings.ReplaceAll(data.MAC, "=", "") != strings.ReplaceAll(calcMac, "=", "") {
|
||||
if strings.TrimRight(data.MAC, "=") != calcMac {
|
||||
return nil, ErrKeyDataMACMismatch
|
||||
}
|
||||
|
||||
utils.XorA256CTR(payload, aesKey, ivBytes)
|
||||
decryptedDecoded, err := base64.StdEncoding.DecodeString(string(payload))
|
||||
decryptedDecoded, err := base64.RawStdEncoding.DecodeString(strings.TrimRight(string(payload), "="))
|
||||
return decryptedDecoded, err
|
||||
}
|
||||
|
|
|
@ -19,9 +19,14 @@ import (
|
|||
type KeyMetadata struct {
|
||||
id string
|
||||
|
||||
Algorithm Algorithm `json:"algorithm"`
|
||||
IV string `json:"iv"`
|
||||
MAC string `json:"mac"`
|
||||
Name string `json:"name"`
|
||||
Algorithm Algorithm `json:"algorithm"`
|
||||
|
||||
// Note: as per https://spec.matrix.org/v1.9/client-server-api/#msecret_storagev1aes-hmac-sha2,
|
||||
// these fields are "maybe padded" base64, so both unpadded and padded values must be supported.
|
||||
IV string `json:"iv"`
|
||||
MAC string `json:"mac"`
|
||||
|
||||
Passphrase *PassphraseMetadata `json:"passphrase,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -59,7 +64,7 @@ func (kd *KeyMetadata) VerifyRecoveryKey(recoverKey string) (*Key, error) {
|
|||
|
||||
// VerifyKey verifies the SSSS key is valid by calculating and comparing its MAC.
|
||||
func (kd *KeyMetadata) VerifyKey(key []byte) bool {
|
||||
return strings.ReplaceAll(kd.MAC, "=", "") == strings.ReplaceAll(kd.calculateHash(key), "=", "")
|
||||
return strings.TrimRight(kd.MAC, "=") == kd.calculateHash(key)
|
||||
}
|
||||
|
||||
// calculateHash calculates the hash used for checking if the key is entered correctly as described
|
||||
|
@ -68,7 +73,7 @@ func (kd *KeyMetadata) calculateHash(key []byte) string {
|
|||
aesKey, hmacKey := utils.DeriveKeysSHA256(key, "")
|
||||
|
||||
var ivBytes [utils.AESCTRIVLength]byte
|
||||
_, _ = base64.StdEncoding.Decode(ivBytes[:], []byte(kd.IV))
|
||||
_, _ = base64.RawStdEncoding.Decode(ivBytes[:], []byte(strings.TrimRight(kd.IV, "=")))
|
||||
|
||||
cipher := utils.XorA256CTR(make([]byte, utils.AESCTRKeyLength), aesKey, ivBytes)
|
||||
|
||||
|
|
|
@ -24,8 +24,8 @@ const key1Meta = `
|
|||
"iterations": 500000,
|
||||
"salt": "y863BOoqOadgDp8S3FtHXikDJEalsQ7d"
|
||||
},
|
||||
"iv": "xxkTK0L4UzxgAFkQ6XPwsw==",
|
||||
"mac": "MEhooO0ZhFJNxUhvRMSxBnJfL20wkLgle3ocY0ee/eA="
|
||||
"iv": "xxkTK0L4UzxgAFkQ6XPwsw",
|
||||
"mac": "MEhooO0ZhFJNxUhvRMSxBnJfL20wkLgle3ocY0ee/eA"
|
||||
}
|
||||
`
|
||||
const key1ID = "gEJqbfSEMnP5JXXcukpXEX1l0aI3MDs0"
|
||||
|
|
|
@ -47,6 +47,8 @@ const (
|
|||
)
|
||||
|
||||
type EncryptedKeyData struct {
|
||||
// Note: as per https://spec.matrix.org/v1.9/client-server-api/#msecret_storagev1aes-hmac-sha2-1,
|
||||
// these fields are "maybe padded" base64, so both unpadded and padded values must be supported.
|
||||
Ciphertext string `json:"ciphertext"`
|
||||
IV string `json:"iv"`
|
||||
MAC string `json:"mac"`
|
||||
|
|
|
@ -124,9 +124,9 @@ func EncodeBase58RecoveryKey(key []byte) string {
|
|||
return spacedKey
|
||||
}
|
||||
|
||||
// HMACSHA256B64 calculates the base64 of the SHA256 hmac of the input with the given key.
|
||||
// HMACSHA256B64 calculates the unpadded base64 of the SHA256 hmac of the input with the given key.
|
||||
func HMACSHA256B64(input []byte, hmacKey [HMACKeyLength]byte) string {
|
||||
h := hmac.New(sha256.New, hmacKey[:])
|
||||
h.Write(input)
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
return base64.RawStdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ func TestKeyDerivationAndHMAC(t *testing.T) {
|
|||
}
|
||||
|
||||
calcMac := HMACSHA256B64(ciphertextBytes, hmacKey)
|
||||
expectedMac := "0DABPNIZsP9iTOh1o6EM0s7BfHHXb96dN7Eca88jq2E="
|
||||
expectedMac := "0DABPNIZsP9iTOh1o6EM0s7BfHHXb96dN7Eca88jq2E"
|
||||
if calcMac != expectedMac {
|
||||
t.Errorf("Expected MAC `%v`, got `%v`", expectedMac, calcMac)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue