1
0
Fork 0

🚧 Saved WIP state of passport

This commit is contained in:
Maxim Lebedev 2018-10-12 18:51:46 +05:00
parent 3439346e60
commit 78dfe7c63d
No known key found for this signature in database
GPG Key ID: F8978F46FF0FFA4F
9 changed files with 645 additions and 0 deletions

View File

@ -1,6 +1,152 @@
package telegram
type (
Fields struct {
PersonalDetails *PersonalDetails
Passport *Passport
InternalPassport *InternalPassport
DriverLicense *DriverLicense
IdentityCard *IdentityCard
Address *ResidentialAddress
UtilityBill *UtilityBill
BankStatement *BankStatement
RentalAgreement *RentalAgreement
PassportRegistration *PassportRegistration
TemporaryRegistration *TemporaryRegistration
PhoneNumber PhoneNumber
Email Email
}
// Passport represent passport.
Passport struct {
Data *IdDocumentData
FrontSide *PassportFile
Selfie *PassportFile
Translation []PassportFile
}
// InternalPassport represent internal passport.
InternalPassport struct {
Data *IdDocumentData
FrontSide *PassportFile
Selfie *PassportFile
Translation []PassportFile
}
// DriverLicense represent driver license.
DriverLicense struct {
Passport
ReverseSide *PassportFile
}
// IdentityCard represent identity card.
IdentityCard struct {
Data *IdDocumentData
FrontSide *PassportFile
ReverseSide *PassportFile
Selfie *PassportFile
Translation []PassportFile
}
// UtilityBill represent utility bill.
UtilityBill struct {
Files []PassportFile
Translation []PassportFile
}
// BankStatement represent bank statement.
BankStatement struct {
Files []PassportFile
Translation []PassportFile
}
// RentalAgreement represent rental agreement.
RentalAgreement struct {
Files []PassportFile
Translation []PassportFile
}
// PassportRegistration represent registration Page in the internal passport.
PassportRegistration struct {
Files []PassportFile
Translation []PassportFile
}
// TemporaryRegistration represent temporary registration.
TemporaryRegistration struct {
Files []PassportFile
Translation []PassportFile
}
// PhoneNumber represent phone number.
PhoneNumber string
// Email represent email.
Email string
// PersonalDetails represents personal details.
PersonalDetails struct {
// First Name
FirstName string `json:"first_name"`
// Last Name
LastName string `json:"last_name"`
// Middle Name
MiddleName string `json:"middle_name,omitempty"`
// Date of birth in DD.MM.YYYY format
BirthDate string `json:"birth_date"`
// Gender, male or female
Gender string `json:"gender"`
// Citizenship (ISO 3166-1 alpha-2 country code)
CountryCode string `json:"country_code"`
// Country of residence (ISO 3166-1 alpha-2 country code)
ResidenceCountryCode string `json:"residence_country_code"`
// First Name in the language of the user's country of residence
FirstNameNative string `json:"first_name_native"`
// Last Name in the language of the user's country of residence
LastNameNative string `json:"last_name_native"`
// Middle Name in the language of the user's country of residence
MiddleNameNative string `json:"middle_name_native,omitempty"`
}
// ResidentialAddress represents a residential address.
ResidentialAddress struct {
// First line for the address
StreetLine1 string `json:"street_line1"`
// Second line for the address
StreetLine2 string `json:"street_line2,omitempty"`
// City
City string `json:"city"`
// State
State string `json:"state,omitempty"`
// ISO 3166-1 alpha-2 country code
CountryCode string `json:"country_code"`
// Address post code
PostCode string `json:"post_code"`
}
// IdDocumentData represents the data of an identity document.
IdDocumentData struct {
// Document number
DocumentNo string `json:"document_no"`
// Date of expiry, in DD.MM.YYYY format
ExpiryDate string `json:"expiry_date,omitempty"`
}
// PassportData contains information about Telegram Passport data shared with
// the bot by the user.
PassportData struct {
@ -26,6 +172,96 @@ type (
FileDate int64 `json:"file_date"`
}
// Credentials is a JSON-serialized object.
Credentials struct {
// Credentials for encrypted data
SecureData *SecureData `json:"secure_data"`
// Bot-specified nonce
Nonce string `json:"nonce"`
}
// SecureData represents the credentials required to decrypt encrypted
// data. All fields are optional and depend on fields that were requested.
SecureData struct {
// Credentials for encrypted personal details
PersonalDetails *SecureValue `json:"personal_details,omitempty"`
// Credentials for encrypted passport
Passport *SecureValue `json:"passport,omitempty"`
// Credentials for encrypted internal passport
InternalPassport *SecureValue `json:"internal_passport,omitempty"`
// Credentials for encrypted driver license
DriverLicense *SecureValue `json:"driver_license,omitempty"`
// Credentials for encrypted ID card
IdentityCard *SecureValue `json:"identity_card,omitempty"`
// Credentials for encrypted residential address
Address *SecureValue `json:"address,omitempty"`
// Credentials for encrypted utility bill
UtilityBill *SecureValue `json:"utility_bill,omitempty"`
// Credentials for encrypted bank statement
BankStatement *SecureValue `json:"bank_statement,omitempty"`
// Credentials for encrypted rental agreement
RentalAgreement *SecureValue `json:"rental_agreement,omitempty"`
// Credentials for encrypted registration from internal passport
PassportRegistration *SecureValue `json:"passport_registration,omitempty"`
// Credentials for encrypted temporary registration
TemporaryRegistration *SecureValue `json:"temporary_registration,omitempty"`
}
// SecureValue represents the credentials required to decrypt encrypted
// values. All fields are optional and depend on the type of fields that
// were requested.
SecureValue struct {
// Credentials for encrypted Telegram Passport data.
Data *DataCredentials `json:"data,omitempty"`
// Credentials for an encrypted document's front side.
FrontSide *FileCredentials `json:"front_side,omitempty"`
// Credentials for an encrypted document's reverse side.
ReverseSide *FileCredentials `json:"reverse_side,omitempty"`
// Credentials for an encrypted selfie of the user with a document.
Selfie *FileCredentials `json:"selfie,omitempty"`
// Credentials for an encrypted translation of the document.
Translation []FileCredentials `json:"translation,omitempty"`
// Credentials for encrypted files.
Files []FileCredentials `json:"files,omitempty"`
}
// DataCredentials can be used to decrypt encrypted data from the data
// field in EncryptedPassportElement.
DataCredentials struct {
// Checksum of encrypted data
DataHash string `json:"data_hash"`
// Secret of encrypted data
Secret string `json:"secret"`
}
// FileCredentials can be used to decrypt encrypted files from the
// front_side, reverse_side, selfie, files and translation fields in
// EncryptedPassportElement.
FileCredentials struct {
// Checksum of encrypted file
FileHash string `json:"file_hash"`
// Secret of encrypted file
Secret string `json:"secret"`
}
// EncryptedPassportElement contains information about documents or other
// Telegram Passport elements shared with the bot by the user.
EncryptedPassportElement struct {
@ -67,6 +303,18 @@ type (
// "identity_card" and "internal_passport". The file can be decrypted
// and verified using the accompanying EncryptedCredentials.
Selfie *PassportFile `json:"selfie,omitempty"`
// Array of encrypted files with translated versions of documents
// provided by the user. Available if requested for “passport”,
// “driver_license”, “identity_card”, “internal_passport”,
// “utility_bill”, “bank_statement”, “rental_agreement”,
// “passport_registration” and “temporary_registration” types.
// Files can be decrypted and verified using the accompanying
// EncryptedCredentials.
Translation []PassportFile `json:"translation,omitempty"`
// Base64-encoded element hash for using in PassportElementErrorUnspecified
Hash string `json:"hash"`
}
// EncryptedCredentials contains data required for decrypting and

View File

@ -165,3 +165,41 @@ func (b *Bot) NewRedirectURL(param string, group bool) *http.URI {
return link
}
func (b *Bot) DecryptPassportFile(pf *PassportFile, fc *FileCredentials) (data []byte, err error) {
secret, err := decodeField(fc.Secret)
if err != nil {
return
}
hash, err := decodeField(fc.FileHash)
if err != nil {
return
}
key, iv := decryptSecretHash(secret, hash)
file, err := b.GetFile(pf.FileID)
if err != nil {
return
}
_, data, err = b.Client.Get(nil, b.NewFileURL(file.FilePath).String())
if err != nil {
return
}
data, err = decryptData(key, iv, data)
if err != nil {
return
}
if !match(hash, data) {
err = ErrNotEqual
return
}
offset := int(data[0])
data = data[offset:]
return
}

41
utils_data_credentials.go Normal file
View File

@ -0,0 +1,41 @@
package telegram
import "errors"
var ErrNotEqual = errors.New("credentials hash and credentials data hash is not equal")
func (dc *DataCredentials) decrypt(d string) (data []byte, err error) {
secret, err := decodeField(dc.Secret)
if err != nil {
return
}
hash, err := decodeField(dc.DataHash)
if err != nil {
return
}
key, iv := decryptSecretHash(secret, hash)
if err != nil {
return
}
data, err = decodeField(d)
if err != nil {
return
}
data, err = decryptData(key, iv, data)
if err != nil {
return
}
if !match(hash, data) {
err = ErrNotEqual
}
offset := int(data[0])
data = data[offset:]
return
}

View File

@ -0,0 +1,22 @@
package telegram
import (
"crypto/rsa"
json "github.com/pquerna/ffjson/ffjson"
)
func (ec *EncryptedCredentials) Decrypt(pk *rsa.PrivateKey) (*Credentials, error) {
if ec == nil || pk == nil {
return nil, nil
}
data, err := decrypt(pk, ec.Secret, ec.Hash, ec.Data)
if err != nil {
return nil, err
}
var c Credentials
err = json.Unmarshal(data, &c)
return &c, err
}

View File

@ -0,0 +1,117 @@
package telegram
import (
"strings"
json "github.com/pquerna/ffjson/ffjson"
)
func (epe *EncryptedPassportElement) DecryptPersonalDetails(sv *SecureValue) (*PersonalDetails, error) {
if !epe.IsPersonalDetails() || !sv.HasData() {
return nil, nil
}
body, err := sv.Data.decrypt(epe.Data)
if err != nil {
return nil, err
}
var pd PersonalDetails
err = json.Unmarshal(body, &pd)
return &pd, err
}
func (epe *EncryptedPassportElement) DecryptPassport(sv *SecureValue, b *Bot) (*Passport, error) {
if !epe.IsPassport() || !sv.HasData() || !sv.HasFrontSide() {
return nil, nil
}
/*
var p Passport
body, err := sv.Data.decrypt(epe.Data)
if err != nil {
return nil, err
}
if err = json.Unmarshal(body, &p.Data); err != nil {
return nil, err
}
p.FrontSide, err = b.DecryptPassportFile(epe.FrontSide, sv.FrontSide)
if err != nil {
return nil, err
}
if sv.HasSelfie() {
p.Selfie, err = b.DecryptPassportFile(epe.Selfie, sv.Selfie)
if err != nil {
return nil, err
}
}
if sv.HasTranslation() {
p.Translation = make([][]byte, len(sv.Translation))
for i := range sv.Translation {
p.Translation[i], err = b.DecryptPassportFile(epe.Translation[i], sv.Translation[i])
if err != nil {
return nil, err
}
}
}
*/
return nil, nil
}
func (epe *EncryptedPassportElement) IsAddress() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeAddress)
}
func (epe *EncryptedPassportElement) IsBankStatement() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeBankStatement)
}
func (epe *EncryptedPassportElement) IsDriverLicense() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeDriverLicense)
}
func (epe *EncryptedPassportElement) IsEmail() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeEmail)
}
func (epe *EncryptedPassportElement) IsIdentityCard() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeIdentityCard)
}
func (epe *EncryptedPassportElement) IsInternalPassport() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeInternalPassport)
}
func (epe *EncryptedPassportElement) IsPassport() bool {
return epe != nil && strings.EqualFold(epe.Type, TypePassport)
}
func (epe *EncryptedPassportElement) IsPassportRegistration() bool {
return epe != nil && strings.EqualFold(epe.Type, TypePassportRegistration)
}
func (epe *EncryptedPassportElement) IsPersonalDetails() bool {
return epe != nil && strings.EqualFold(epe.Type, TypePersonalDetails)
}
func (epe *EncryptedPassportElement) IsPhoneNumber() bool {
return epe != nil && strings.EqualFold(epe.Type, TypePhoneNumber)
}
func (epe *EncryptedPassportElement) IsRentalAgreement() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeRentalAgreement)
}
func (epe *EncryptedPassportElement) IsTemporaryRegistration() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeTemporaryRegistration)
}
func (epe *EncryptedPassportElement) IsUtilityBill() bool {
return epe != nil && strings.EqualFold(epe.Type, TypeUtilityBill)
}

16
utils_id_document_data.go Normal file
View File

@ -0,0 +1,16 @@
package telegram
import "time"
func (idd *IdDocumentData) ExpiryTime() *time.Time {
if idd == nil || idd.ExpiryDate == "" {
return nil
}
et, err := time.Parse("02.01.2006", idd.ExpiryDate)
if err != nil {
return nil
}
return &et
}

106
utils_passport.go Normal file
View File

@ -0,0 +1,106 @@
package telegram
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
)
func decrypt(pk *rsa.PrivateKey, s, h, d string) (obj []byte, err error) {
// Note that all base64-encoded fields should be decoded before use.
secret, err := decodeField(s)
if err != nil {
return
}
hash, err := decodeField(h)
if err != nil {
return
}
data, err := decodeField(d)
if err != nil {
return
}
if pk != nil {
// Decrypt the credentials secret (secret field in EncryptedCredentials)
// using your private key
secret, err = decryptSecret(pk, secret)
if err != nil {
return
}
}
// Use this secret and the credentials hash (hash field in
// EncryptedCredentials) to calculate credentials_key and credentials_iv
key, iv := decryptSecretHash(secret, hash)
if err != nil {
return
}
// Decrypt the credentials data (data field in EncryptedCredentials) by
// AES256-CBC using these credentials_key and credentials_iv.
data, err = decryptData(key, iv, data)
if err != nil {
return
}
// IMPORTANT: At this step, make sure that the credentials hash is equal
// to SHA256(credentials_data)
if !match(hash, data) {
err = ErrNotEqual
return
}
// Credentials data is padded with 32 to 255 random padding bytes to make
// its length divisible by 16 bytes. The first byte contains the length
// of this padding (including this byte). Remove the padding to get the
// data.
offset := int(data[0])
data = data[offset:]
return
}
func decodeField(rawField string) (field []byte, err error) {
return base64.StdEncoding.DecodeString(rawField)
}
func decryptSecret(pk *rsa.PrivateKey, s []byte) (secret []byte, err error) {
return rsa.DecryptOAEP(sha1.New(), rand.Reader, pk, s, nil)
}
func decryptSecretHash(s, h []byte) (key, iv []byte) {
hash := sha512.New()
hash.Write(s)
hash.Write(h)
sh := hash.Sum(nil)
return sh[0:32], sh[32 : 32+16]
}
func match(h, d []byte) bool {
dh := sha256.New()
dh.Write(d)
return bytes.EqualFold(h, dh.Sum(nil))
}
func decryptData(key, iv, data []byte) (buf []byte, err error) {
block, err := aes.NewCipher(key)
if err != nil {
return
}
buf = make([]byte, len(data))
cipher.NewCBCDecrypter(block, iv).CryptBlocks(buf, data)
return
}

32
utils_personal_details.go Normal file
View File

@ -0,0 +1,32 @@
package telegram
import "time"
func (pd *PersonalDetails) BirthTime() *time.Time {
if pd == nil || pd.BirthDate == "" {
return nil
}
bt, err := time.Parse("02.01.2006", pd.BirthDate)
if err != nil {
return nil
}
return &bt
}
func (pd *PersonalDetails) FullName() string {
if pd == nil {
return ""
}
return pd.FirstName + " " + pd.LastName
}
func (pd *PersonalDetails) FullNameNative() string {
if pd == nil {
return ""
}
return pd.FirstNameNative + " " + pd.LastNameNative
}

25
utils_secure_value.go Normal file
View File

@ -0,0 +1,25 @@
package telegram
func (sv *SecureValue) HasData() bool {
return sv != nil && sv.Data != nil
}
func (sv *SecureValue) HasFiles() bool {
return sv != nil && len(sv.Files) > 0
}
func (sv *SecureValue) HasFrontSide() bool {
return sv != nil && sv.FrontSide != nil
}
func (sv *SecureValue) HasReverseSide() bool {
return sv != nil && sv.ReverseSide != nil
}
func (sv *SecureValue) HasSelfie() bool {
return sv != nil && sv.Selfie != nil
}
func (sv *SecureValue) HasTranslation() bool {
return sv != nil && len(sv.Translation) > 0
}