// mautrix-signal - A Matrix-signal puppeting bridge. // Copyright (C) 2023 Scott Weber // Copyright (C) 2025 Tulir Asokan // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package libsignalgo /* #include "./libsignal-ffi.h" #include */ import "C" import ( "encoding/base64" "errors" "runtime" "unsafe" "github.com/google/uuid" "go.mau.fi/util/random" ) const ProfileKeyLength = 32 const AccessKeyLength = 16 const ProfileKeyVersionLength = 64 type ProfileKey [ProfileKeyLength]byte type ProfileKeyCommitment = fixedArray97 type ProfileKeyVersion [ProfileKeyVersionLength]byte type AccessKey [AccessKeyLength]byte func (pk *ProfileKey) cFixedArray() *C.SignalType_FixedArray32_uint8_t { return (*C.SignalType_FixedArray32_uint8_t)(unsafe.Pointer(pk)) } func (pk *ProfileKey) cConstFixedArray() cFixedArray32Compat { return cFixedArray32Compat(pk.cFixedArray()) } func (pkv *ProfileKeyVersion) cFixedArray() *C.SignalType_FixedArray64_uint8_t { return (*C.SignalType_FixedArray64_uint8_t)(unsafe.Pointer(pkv)) } func (pkv *ProfileKeyVersion) cConstFixedArray() cFixedArray64Compat { return cFixedArray64Compat(pkv.cFixedArray()) } func (ak *AccessKey) cFixedArray() *C.SignalType_FixedArray16_uint8_t { return (*C.SignalType_FixedArray16_uint8_t)(unsafe.Pointer(ak)) } func (ak *AccessKey) cConstFixedArray() cFixedArray16Compat { return cFixedArray16Compat(ak.cFixedArray()) } func DeserializeProfileKey(bytes []byte) (*ProfileKey, error) { if len(bytes) == 0 { return nil, nil } else if len(bytes) != ProfileKeyLength { return nil, errors.New("invalid profile key length") } key := ProfileKey(bytes) return &key, nil } var blankProfileKey ProfileKey func (pk *ProfileKey) IsEmpty() bool { return pk == nil || *pk == blankProfileKey } func (pv *ProfileKeyVersion) String() string { return string(pv[:]) } func (pk *ProfileKey) Slice() []byte { if pk.IsEmpty() { return nil } return pk[:] } func (ak *AccessKey) Xor(other *AccessKey) *AccessKey { if ak == nil { return other } else if other == nil { return ak } var result AccessKey for i := 0; i < AccessKeyLength; i++ { result[i] = ak[i] ^ other[i] } return &result } func (ak *AccessKey) String() string { return base64.StdEncoding.EncodeToString(ak[:]) } func (pk *ProfileKey) GetCommitment(u uuid.UUID) (*ProfileKeyCommitment, error) { var result ProfileKeyCommitment c_uuid := NewACIServiceID(u).cConstFixedArray() signalFfiError := C.signal_profile_key_get_commitment( result.cFixedArray(), pk.cConstFixedArray(), c_uuid, ) runtime.KeepAlive(pk) runtime.KeepAlive(u) if signalFfiError != nil { return nil, wrapError(signalFfiError) } return &result, nil } func (pk *ProfileKey) GetProfileKeyVersion(u uuid.UUID) (*ProfileKeyVersion, error) { var result ProfileKeyVersion c_uuid := NewACIServiceID(u).cConstFixedArray() signalFfiError := C.signal_profile_key_get_profile_key_version( result.cFixedArray(), pk.cConstFixedArray(), c_uuid, ) runtime.KeepAlive(pk) runtime.KeepAlive(u) if signalFfiError != nil { return nil, wrapError(signalFfiError) } return &result, nil } func (pk *ProfileKey) DeriveAccessKey() (*AccessKey, error) { var result AccessKey signalFfiError := C.signal_profile_key_derive_access_key( result.cFixedArray(), pk.cConstFixedArray(), ) runtime.KeepAlive(pk) if signalFfiError != nil { return nil, wrapError(signalFfiError) } return &result, nil } type ProfileKeyCredentialRequestContext [473]byte type ProfileKeyCredentialRequest = fixedArray329 type ProfileKeyCredentialResponse []byte type ProfileKeyCredentialPresentation []byte type ExpiringProfileKeyCredential = fixedArray153 type ExpiringProfileKeyCredentialResponse = fixedArray497 func (p *ProfileKeyCredentialRequestContext) cFixedArray() *C.SignalType_FixedArray473_uint8_t { return (*C.SignalType_FixedArray473_uint8_t)(unsafe.Pointer(p)) } func (p *ProfileKeyCredentialRequestContext) cConstFixedArray() cFixedArray473Compat { return cFixedArray473Compat(p.cFixedArray()) } func CreateProfileKeyCredentialRequestContext(serverPublicParams *ServerPublicParams, u uuid.UUID, profileKey ProfileKey) (*ProfileKeyCredentialRequestContext, error) { var result ProfileKeyCredentialRequestContext randBytes := Randomness(random.Bytes(RandomnessLength)) c_uuid := NewACIServiceID(u).cConstFixedArray() signalFfiError := C.signal_server_public_params_create_profile_key_credential_request_context_deterministic( result.cFixedArray(), C.SignalConstPointerServerPublicParams{serverPublicParams}, randBytes.cConstFixedArray(), c_uuid, profileKey.cConstFixedArray(), ) runtime.KeepAlive(u) runtime.KeepAlive(profileKey) runtime.KeepAlive(randBytes) if signalFfiError != nil { return nil, wrapError(signalFfiError) } return &result, nil } func (p *ProfileKeyCredentialRequestContext) ProfileKeyCredentialRequestContextGetRequest() (*ProfileKeyCredentialRequest, error) { var result ProfileKeyCredentialRequest signalFfiError := C.signal_profile_key_credential_request_context_get_request( result.cFixedArray(), p.cConstFixedArray(), ) runtime.KeepAlive(p) if signalFfiError != nil { return nil, wrapError(signalFfiError) } return &result, nil } func NewExpiringProfileKeyCredentialResponse(b []byte) (*ExpiringProfileKeyCredentialResponse, error) { borrowedBuffer := BytesToBuffer(b) signalFfiError := C.signal_expiring_profile_key_credential_response_check_valid_contents(borrowedBuffer) runtime.KeepAlive(b) if signalFfiError != nil { return nil, wrapError(signalFfiError) } response := ExpiringProfileKeyCredentialResponse(b) return &response, nil } func ReceiveExpiringProfileKeyCredential(spp *ServerPublicParams, requestContext *ProfileKeyCredentialRequestContext, response *ExpiringProfileKeyCredentialResponse, currentTimeInSeconds uint64) (*ExpiringProfileKeyCredential, error) { var credential ExpiringProfileKeyCredential signalFfiError := C.signal_server_public_params_receive_expiring_profile_key_credential( credential.cFixedArray(), C.SignalConstPointerServerPublicParams{spp}, requestContext.cConstFixedArray(), response.cConstFixedArray(), (C.uint64_t)(currentTimeInSeconds), ) runtime.KeepAlive(requestContext) runtime.KeepAlive(response) runtime.KeepAlive(currentTimeInSeconds) if signalFfiError != nil { return nil, wrapError(signalFfiError) } return &credential, nil } func (a ProfileKeyCredentialPresentation) CheckValidContents() error { signalFfiError := C.signal_profile_key_credential_presentation_check_valid_contents(BytesToBuffer(a)) runtime.KeepAlive(a) return wrapError(signalFfiError) } func (a ProfileKeyCredentialPresentation) UUIDCiphertext() (UUIDCiphertext, error) { var out UUIDCiphertext signalFfiError := C.signal_profile_key_credential_presentation_get_uuid_ciphertext(out.cFixedArray(), BytesToBuffer(a)) runtime.KeepAlive(a) if signalFfiError != nil { return UUIDCiphertext{}, wrapError(signalFfiError) } return out, nil } func (a ProfileKeyCredentialPresentation) ProfileKeyCiphertext() (ProfileKeyCiphertext, error) { var out ProfileKeyCiphertext signalFfiError := C.signal_profile_key_credential_presentation_get_profile_key_ciphertext(out.cFixedArray(), BytesToBuffer(a)) runtime.KeepAlive(a) if signalFfiError != nil { return ProfileKeyCiphertext{}, wrapError(signalFfiError) } return out, nil }