add vendoring
This commit is contained in:
150
vendor/maunium.net/go/mautrix/crypto/keyimport.go
generated
vendored
Normal file
150
vendor/maunium.net/go/mautrix/crypto/keyimport.go
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) 2020 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"maunium.net/go/mautrix/crypto/olm"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingExportPrefix = errors.New("invalid Matrix key export: missing prefix")
|
||||
ErrMissingExportSuffix = errors.New("invalid Matrix key export: missing suffix")
|
||||
ErrUnsupportedExportVersion = errors.New("unsupported Matrix key export format version")
|
||||
ErrMismatchingExportHash = errors.New("mismatching hash; incorrect passphrase?")
|
||||
ErrInvalidExportedAlgorithm = errors.New("session has unknown algorithm")
|
||||
ErrMismatchingExportedSessionID = errors.New("imported session has different ID than expected")
|
||||
)
|
||||
|
||||
var exportPrefixBytes, exportSuffixBytes = []byte(exportPrefix), []byte(exportSuffix)
|
||||
|
||||
func decodeKeyExport(data []byte) ([]byte, error) {
|
||||
// If the valid prefix and suffix aren't there, it's probably not a Matrix key export
|
||||
if !bytes.HasPrefix(data, exportPrefixBytes) {
|
||||
return nil, ErrMissingExportPrefix
|
||||
} else if !bytes.HasSuffix(data, exportSuffixBytes) {
|
||||
return nil, ErrMissingExportSuffix
|
||||
}
|
||||
// Remove the prefix and suffix, we don't care about them anymore
|
||||
data = data[len(exportPrefix) : len(data)-len(exportSuffix)]
|
||||
|
||||
// Allocate space for the decoded data. Ignore newlines when counting the length
|
||||
exportData := make([]byte, base64.StdEncoding.DecodedLen(len(data)-bytes.Count(data, []byte{'\n'})))
|
||||
n, err := base64.StdEncoding.Decode(exportData, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exportData[:n], nil
|
||||
}
|
||||
|
||||
func decryptKeyExport(passphrase string, exportData []byte) ([]ExportedSession, error) {
|
||||
if exportData[0] != exportVersion1 {
|
||||
return nil, ErrUnsupportedExportVersion
|
||||
}
|
||||
|
||||
// Get all the different parts of the export
|
||||
salt := exportData[1:17]
|
||||
iv := exportData[17:33]
|
||||
passphraseRounds := binary.BigEndian.Uint32(exportData[33:37])
|
||||
dataWithoutHashLength := len(exportData) - exportHashLength
|
||||
encryptedData := exportData[exportHeaderLength:dataWithoutHashLength]
|
||||
hash := exportData[dataWithoutHashLength:]
|
||||
|
||||
// Compute the encryption and hash keys from the passphrase and salt
|
||||
encryptionKey, hashKey := computeKey(passphrase, salt, int(passphraseRounds))
|
||||
|
||||
// Compute and verify the hash. If it doesn't match, the passphrase is probably wrong
|
||||
mac := hmac.New(sha256.New, hashKey)
|
||||
mac.Write(exportData[:dataWithoutHashLength])
|
||||
if !bytes.Equal(hash, mac.Sum(nil)) {
|
||||
return nil, ErrMismatchingExportHash
|
||||
}
|
||||
|
||||
// Decrypt the export
|
||||
block, _ := aes.NewCipher(encryptionKey)
|
||||
unencryptedData := make([]byte, len(exportData)-exportHashLength-exportHeaderLength)
|
||||
cipher.NewCTR(block, iv).XORKeyStream(unencryptedData, encryptedData)
|
||||
|
||||
// Parse the decrypted JSON
|
||||
var sessionsJSON []ExportedSession
|
||||
err := json.Unmarshal(unencryptedData, &sessionsJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid export json: %w", err)
|
||||
}
|
||||
return sessionsJSON, nil
|
||||
}
|
||||
|
||||
func (mach *OlmMachine) importExportedRoomKey(session ExportedSession) (bool, error) {
|
||||
if session.Algorithm != id.AlgorithmMegolmV1 {
|
||||
return false, ErrInvalidExportedAlgorithm
|
||||
}
|
||||
|
||||
igsInternal, err := olm.InboundGroupSessionImport([]byte(session.SessionKey))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to import session: %w", err)
|
||||
} else if igsInternal.ID() != session.SessionID {
|
||||
return false, ErrMismatchingExportedSessionID
|
||||
}
|
||||
igs := &InboundGroupSession{
|
||||
Internal: *igsInternal,
|
||||
SigningKey: session.SenderClaimedKeys.Ed25519,
|
||||
SenderKey: session.SenderKey,
|
||||
RoomID: session.RoomID,
|
||||
// TODO should we add something here to mark the signing key as unverified like key requests do?
|
||||
ForwardingChains: session.ForwardingChains,
|
||||
}
|
||||
existingIGS, _ := mach.CryptoStore.GetGroupSession(igs.RoomID, igs.SenderKey, igs.ID())
|
||||
if existingIGS != nil && existingIGS.Internal.FirstKnownIndex() <= igs.Internal.FirstKnownIndex() {
|
||||
// We already have an equivalent or better session in the store, so don't override it.
|
||||
return false, nil
|
||||
}
|
||||
err = mach.CryptoStore.PutGroupSession(igs.RoomID, igs.SenderKey, igs.ID(), igs)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to store imported session: %w", err)
|
||||
}
|
||||
mach.markSessionReceived(igs.ID())
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ImportKeys imports data that was exported with the format specified in the Matrix spec.
|
||||
// See https://spec.matrix.org/v1.2/client-server-api/#key-exports
|
||||
func (mach *OlmMachine) ImportKeys(passphrase string, data []byte) (int, int, error) {
|
||||
exportData, err := decodeKeyExport(data)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
sessions, err := decryptKeyExport(passphrase, exportData)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
count := 0
|
||||
for _, session := range sessions {
|
||||
imported, err := mach.importExportedRoomKey(session)
|
||||
if err != nil {
|
||||
mach.Log.Warn("Failed to import Megolm session %s/%s from file: %v", session.RoomID, session.SessionID, err)
|
||||
} else if imported {
|
||||
mach.Log.Debug("Imported Megolm session %s/%s from file", session.RoomID, session.SessionID)
|
||||
count++
|
||||
} else {
|
||||
mach.Log.Debug("Skipped Megolm session %s/%s: already in store", session.RoomID, session.SessionID)
|
||||
}
|
||||
}
|
||||
return count, len(sessions), nil
|
||||
}
|
||||
Reference in New Issue
Block a user