Browse Source

WIP - Working encryption with seeder

pull/1/head
Tovi Jaeschke-Rogers 2 years ago
parent
commit
5eb1aed5c4
21 changed files with 1776 additions and 73 deletions
  1. +0
    -1
      Backend/Api/Friends/EncryptedFriendsList.go
  2. +4
    -3
      Backend/Api/Friends/FriendRequest.go
  3. +37
    -0
      Backend/Api/Friends/Friends.go
  4. +1
    -1
      Backend/Api/Routes.go
  5. +3
    -2
      Backend/Database/Seeder/FriendSeeder.go
  6. +2
    -2
      Backend/Database/Seeder/MessageSeeder.go
  7. +60
    -10
      Backend/Database/Seeder/Seed.go
  8. +1
    -1
      Backend/Models/Friends.go
  9. +47
    -2
      mobile/lib/models/friends.dart
  10. +979
    -0
      mobile/lib/utils/encryption/crypto_utils.dart
  11. +16
    -13
      mobile/lib/utils/encryption/rsa_key_helper.dart
  12. +463
    -0
      mobile/lib/utils/encryption/string_utils.dart
  13. +35
    -0
      mobile/lib/utils/storage/database.dart
  14. +3
    -3
      mobile/lib/utils/storage/encryption_keys.dart
  15. +38
    -0
      mobile/lib/utils/storage/friends.dart
  16. +23
    -0
      mobile/lib/utils/storage/session_cookie.dart
  17. +21
    -13
      mobile/lib/views/authentication/login.dart
  18. +9
    -12
      mobile/lib/views/authentication/signup.dart
  19. +10
    -8
      mobile/lib/views/main/friend_list.dart
  20. +22
    -1
      mobile/pubspec.lock
  21. +2
    -1
      mobile/pubspec.yaml

+ 0
- 1
Backend/Api/Friends/EncryptedFriendsList.go View File

@ -18,7 +18,6 @@ func EncryptedFriendList(w http.ResponseWriter, r *http.Request) {
) )
userSession, err = Auth.CheckCookie(r) userSession, err = Auth.CheckCookie(r)
if err != nil { if err != nil {
http.Error(w, "Forbidden", http.StatusUnauthorized) http.Error(w, "Forbidden", http.StatusUnauthorized)
return return


+ 4
- 3
Backend/Api/Friends/FriendRequest.go View File

@ -15,8 +15,9 @@ func FriendRequest(w http.ResponseWriter, r *http.Request) {
user Models.User user Models.User
requestBody []byte requestBody []byte
requestJson map[string]interface{} requestJson map[string]interface{}
friendID []byte
friendID string
friendRequest Models.Friend friendRequest Models.Friend
ok bool
err error err error
) )
@ -38,8 +39,8 @@ func FriendRequest(w http.ResponseWriter, r *http.Request) {
return return
} }
friendID, err = Util.ToBytes(requestJson["id"])
if requestJson["id"] == nil {
friendID, ok = requestJson["id"].(string)
if !ok {
http.Error(w, "Error", http.StatusInternalServerError) http.Error(w, "Error", http.StatusInternalServerError)
return return
} }


+ 37
- 0
Backend/Api/Friends/Friends.go View File

@ -2,8 +2,10 @@ package Friends
import ( import (
"encoding/json" "encoding/json"
"io/ioutil"
"net/http" "net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Util" "git.tovijaeschke.xyz/tovi/Envelope/Backend/Util"
) )
@ -30,3 +32,38 @@ func Friend(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write(returnJson) w.Write(returnJson)
} }
func CreateFriendRequest(w http.ResponseWriter, r *http.Request) {
var (
friendData Models.Friend
requestBody []byte
returnJson []byte
err error
)
requestBody, err = ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
err = json.Unmarshal(requestBody, &friendData)
if err != nil {
panic(err)
}
err = Database.CreateFriendRequest(&friendData)
if err != nil {
panic(err)
}
returnJson, err = json.MarshalIndent(friendData, "", " ")
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
w.WriteHeader(http.StatusInternalServerError)
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}

+ 1
- 1
Backend/Api/Routes.go View File

@ -31,7 +31,6 @@ func authenticationMiddleware(next http.Handler) http.Handler {
) )
_, err = Auth.CheckCookie(r) _, err = Auth.CheckCookie(r)
if err != nil { if err != nil {
http.Error(w, "Forbidden", http.StatusUnauthorized) http.Error(w, "Forbidden", http.StatusUnauthorized)
return return
@ -61,6 +60,7 @@ func InitApiEndpoints(router *mux.Router) {
authApi.Use(authenticationMiddleware) authApi.Use(authenticationMiddleware)
// Define routes for friends and friend requests // Define routes for friends and friend requests
authApi.HandleFunc("/friend", Friends.CreateFriendRequest).Methods("POST")
authApi.HandleFunc("/friends", Friends.EncryptedFriendList).Methods("GET") authApi.HandleFunc("/friends", Friends.EncryptedFriendList).Methods("GET")
authApi.HandleFunc("/friend/{userID}", Friends.Friend).Methods("GET") authApi.HandleFunc("/friend/{userID}", Friends.Friend).Methods("GET")
authApi.HandleFunc("/friend/{userID}/request", Friends.FriendRequest).Methods("POST") authApi.HandleFunc("/friend/{userID}/request", Friends.FriendRequest).Methods("POST")


+ 3
- 2
Backend/Database/Seeder/FriendSeeder.go View File

@ -1,6 +1,7 @@
package Seeder package Seeder
import ( import (
"encoding/base64"
"time" "time"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
@ -15,7 +16,7 @@ func seedFriend(user, friendUser Models.User) error {
friend = Models.Friend{ friend = Models.Friend{
UserID: user.ID, UserID: user.ID,
FriendID: encryptWithPublicKey(friendUser.ID.Bytes(), decodedPublicKey),
FriendID: base64.StdEncoding.EncodeToString(encryptWithPublicKey([]byte(friendUser.ID.String()), decodedPublicKey)),
AcceptedAt: time.Now(), AcceptedAt: time.Now(),
} }
@ -26,7 +27,7 @@ func seedFriend(user, friendUser Models.User) error {
friend = Models.Friend{ friend = Models.Friend{
UserID: friendUser.ID, UserID: friendUser.ID,
FriendID: encryptWithPublicKey(user.ID.Bytes(), decodedPublicKey),
FriendID: base64.StdEncoding.EncodeToString(encryptWithPublicKey([]byte(user.ID.String()), decodedPublicKey)),
AcceptedAt: time.Now(), AcceptedAt: time.Now(),
} }


+ 2
- 2
Backend/Database/Seeder/MessageSeeder.go View File

@ -6,7 +6,7 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha512"
"crypto/sha256"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"hash" "hash"
@ -23,7 +23,7 @@ func encryptWithPublicKey(msg []byte, pub *rsa.PublicKey) []byte {
hash hash.Hash hash hash.Hash
) )
hash = sha512.New()
hash = sha256.New()
ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil) ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil)
if err != nil { if err != nil {
panic(err) panic(err)


+ 60
- 10
Backend/Database/Seeder/Seed.go View File

@ -1,33 +1,80 @@
package Seeder package Seeder
import ( import (
"crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"errors"
"log" "log"
) )
const ( const (
// Encrypted with "password" // Encrypted with "password"
encryptedPrivateKey string = `
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
`
publicKey string = `
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAidHsO9H0fMd6cu9dQiOwu/qtaDq+lG59kudoHGx8WZhucpOSp2df95UzjnXmMw1fz0Mx5fjgBaunG7u1MVBy+7IdYwXp+iz3xZulVy1Yv3e+GMzgQhAuxhz/8wsHkFkUTweDZCrCZPhkTXlCrxDIuKykQ0el+RnpSjyyljOsAWAdTmrz0a2Nh9FOmF1v49pWy3Z3aJG58xl1dmFkpXjT3m36GB4Z30iR1uOMnNdrtfwIfLQAc7nmle2LxCHeEMYzlA9y6JChm5kGI6FmglSKYTxvDm40jA5mYyDCPADeCodbIw4Mtm0nwrM3QqKWj+jlaL8BY7/jjaosmz7VK2do4wIDAQAB
-----END PUBLIC KEY-----
`
encryptedPrivateKey string = `sPhQsHpXYFqPb7qdmTY7APFwBb4m7meCITujDeKMQFnIjplOVm9ijjXU+YAmGvrX13ukBj8zo9MTVhjJUjJ917pyLhl4w8uyg1jCvplUYtJVXhGA9Wy3NqHMuq3SU3fKdlEM+oR4zYkbAYWp42XvulbcuVBEWiWkvHOrbdKPFpMmd54SL2c/vcWrmjgC7rTlJf2TYICZwRK+6Y0XZi5fSWeU0vg7+rHWKHc5MHHtAdAiL+HCa90c5gfh+hXkT5ojGHOkhT9kdLy3PTPN19EGpdXgZ3WFq1z9CZ6zX7uM091uR0IvgzfwaLx8HJCx7ViWQhioH9LJZgC73RMf/dwzejg2COy4QT/E59RPOczgd779rxiRmphMoR8xJYBFRlkTVmcUO4NcUE50Cc39hXezcekHuV1YQK4BXTrxGX1ceiCXYlKAWS9wHZpog9OldTCPBpw5XAWExh3kRzqdvsdHxHVE+TpAEIjDljAlc3r+FPHYH1zWWk41eQ/zz3Vkx5Zl4dMF9x+uUOspQXVb/4K42e9fMKychNUN5o/JzIwy7xOzgXa6iwf223On/mXKV6FK6Q8lojK7Wc8g7AwfqnN9//HjI14pVqGBJtn5ggL/g4qt0JFl3pV/6n/ZLMG6k8wpsaApLGvsTPqZHcv+C69Z33rZQ4TagXVxpmnWMpPCaR0+Dawn4iAce2UvUtIN2KbJNcTtRQo4z30+BbgmVKHgkR0EHMu4cYjJPYwJ5H8IYcQuFKb7+Cp33FD2Lv54I9uvtVHH9bWcid9K82y68PufJi/0icZ3EyEqZygez9mgJzxXO1b7xZMiosGs82QRv7IIOSzqBPRYv1Lxi3fWkgnOvw4dWFxJnKEI2+KD9K0z+XsgVlm26fdRklQAAf6xOJ1nJXBScbm12FBTWLMjLzHWz/iI9mQ+eGV9AREqrgQjUayXdnCsa0Q9bTTktxBkrJND4NUEDSGklhj9SY+VM0mhgAbkCvSE59vKtcNmCHx2Y+JnbZyKzJ71EaErX9vOpYCneKOjn8phVBJHQHM16QRLGyW4DUfn2CtAvb7Kks56kf/mn9YZDU68zSoLzm9rz7fjS2OUsxwmuv2IRCv/UTGgtfEfCs34qzagADfTNKTou7qkedhoygvuHiN4PzgGnjw1DQMks9PWr44z1gvIV4pEGiqgIuNHDjxKsfgQy0Cp2AV1+FNLWd1zd5t/K2pXR+knDoeHIZ2m6txQMl9I4GIyQ1bQFJWrYXPS8oMjvoH0YYVsHyShBsU2SKlG7nGbuUyoCR1EtRIzHMgP1Dq+Whqdbv67pRvhGVmydkCh0wbD+LJBcp2KJK+EQT9vv6GT5JW0oVHnE5TEXCnEJOW/rMhNMTMSccRmnVdguIE4HZsXx+cmV36jHgEt9bzcsvyWvFFoG4xL+t2UUnztX870vu//XaeVuOEAgehY/KLncrY7lhsQA4puCFIWpPteiCNhU1D8DTKc8V0ZtLT9a31SL1NLhZ+YHiD8Hs5SYdj6FW50E5yYUqPRPkg5mpbh88cRcPdsngCxU8iusNN3MSP07lO0h8zULDqtQsAq9p5o7IFTvWlAjekMy1sKTj3CuH7FuAkMHvwU0odMFeaS9T+8+4OGeprHwogWTzTbPnoOqOP/RC6vGfBvpju5s264hYguT24iXzhDFYk/8JQQe+USIbkQ7wXRw+/9cK8h5cs4LyaxMOx0pXHooxJ01bF8BYgYG4s0RB2gItzMk/L5/XhrOdWxEAdYR27s0dCN58gyvoU6phgQbTqvNTFYAObRcjfKfHu3PrFCYBBAKJ7Nm58C3rz832+ZTGVdQ3490TvO+sCLYKzpgtsqr8KyedG9LKa8wn/wlRD7kYn+J2SrMPY2Q0e4evyJaCAsolp/BQfy9JFtyRDPWTHn+jOHjW8ZN7vswGkRwYlSJSl0UC8mmJyS4lwnO/Vv4wBnDHQEzIycjn3JZAlV5ing0HKqUfW6G07453JXd8oZiMC/kIQjgWkdg34zxBYarVVrHFG5FIH9w7QWY8PCDU/kkcLniT0yD1/gkqAG2HpwaXEcSqX8Ofrbpd/IA7R7iCXYE5Q1mAvSvICpPg9Cf3CHjLyAEDz9cwKnZHkocXC8evdsTf2e7Wz8FFPAI3onFvym0MfZuRrIZitX1V8NOLedd3y74CwuErfzrr60DjyPRxGbJ4llMbm+ojeENe0HBedNm71jf+McSihKbSo5GDBxfVYVreYZ8A4iP0LsxtzQFxuzdeDL5KA9uNNw+LN9FN9vKhdALhQSnSfLPfMBsM/ey7dbxb4eRT0fpApX`
// Private key for testing server side
privateKey string = `-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJScQQJxWxKwqf
FmXH64QnRBVyW7cU25F+O9Zy96dqTjbV4ruWrzb4+txmK20ZPQvMxDLefhEzTXWb
HZV1P/XxgmEpaBVHwHnkhaPzzChOa/G18CDoCNrgyVzh5a31OotTCuGlS1bSkR53
ExPXQq8nPJKqN1tdwAslr4cT61zypKSmZsJa919IZeHL9p1/UMXknfThcR9z3rR4
ll6O7+dmrZzaDdJz39IC38K5yJsThau5hnddpssw76k4rZ9KFFlbWJSHFvWIAhst
lCV0kCwdAmPNVNavzQfvTxWeN1x9uJUstVlg60DRCmRhjC88K77sU+1jp4cp/Uv8
aSGRpytlAgMBAAECggEBALFbJr8YwRs/EnfEU2AI24OBkOgXecSOBq9UaAsavU+E
pPpmceU+c1CEMUhwwQs457m/shaqu9sZSCOpuHP8LGdk+tlyFTYImR5KxoBdBbK7
l9k4QLZSfxELO6TrLBDkSbic4N8098ZHCbHfhF7qKcyHqa8DYaTEPs4wz/M0Mcy0
xziCxMUFh/LhSLDH8PMMXZ+HV3+zmxdEqmaZvk3FQOGD1O39I9TA8PnFa11whVbN
nMSjxgmK+byPIM4LFXNHk+TZsJm1FaYaGVdLetAPET7p6XMrMWy+z/4dcb4GbYjY
0i5Xv1lVlIRgDB9xj0MOW5hzQzTPHC4JN4nIoBFSc20CgYEA5IgymckwqKJJWXRn
AIJ3guuEp4vBtjmdVCJnFmbPEeW+WY+CNuwn9DK78Zavfn1HruryE/hkYLVNPm8y
KSf16+tIadUXcao1UIVDNSVC6jtFmRLgWuPXbNKFQwUor1ai9IK+F3JV8pfr36HE
8rk/LEM0DIgsTg+j+IKT39a7IucCgYEA4XtKGhvnGUdcveMPcrvuQlSnucSpw5Ly
4KuRsTySdMihhxX1GSyg6F2T4YKFRqKZERsYgYk6A32u53If+VkXacvOsInwuoBa
FTb3fOQpw1xBSI7R3RgiriY4cCsDetexEBbg7/SrodpQu254A8+5PKxrSR1U+idx
boX745k1gdMCgYEAuZ7CctTOV/pQ137LdseBqO4BNlE2yvrrBf5XewOQZzoTLQ16
N4ADR765lxXMf1HkmnesnnnflglMr0yEEpepkLDvhT6WpzUXzsoe95jHTBdOhXGm
l0x+mp43rWMQU7Jr82wKWGL+2md5J5ButrOuUxZWvWMRkWn0xhHRaDsyjrsCgYAq
zNRMEG/VhI4+HROZm8KmJJuRz5rJ3OLtcqO9GNpUAKFomupjVO1WLi0b6UKTHdog
PRxxujKg5wKEPE2FbzvagS1CpWxkemifDkf8FPM4ehKKS1HavfIXTHn6ELAgaUDa
5Pzdj3vkxSP98AIn9w4aTkAvKLowobwOVrBxi2t0sQKBgHh2TrGSnlV3s1DijfNM
0JiwsHWz0hljybcZaZP45nsgGRiR15TcIiOLwkjaCws2tYtOSOT4sM7HV/s2mpPa
b0XvaLzh1iKG7HZ9tvPt/VhHlKKosNBK/j4fvgMZg7/bhRfHmaDQKoqlGbtyWjEQ
mj1b2/Gnbk3VYDR16BFfj7m2
-----END PRIVATE KEY-----`
publicKey string = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyUnEECcVsSsKnxZlx+uE
J0QVclu3FNuRfjvWcvenak421eK7lq82+PrcZittGT0LzMQy3n4RM011mx2VdT/1
8YJhKWgVR8B55IWj88woTmvxtfAg6Aja4Mlc4eWt9TqLUwrhpUtW0pEedxMT10Kv
JzySqjdbXcALJa+HE+tc8qSkpmbCWvdfSGXhy/adf1DF5J304XEfc960eJZeju/n
Zq2c2g3Sc9/SAt/CucibE4WruYZ3XabLMO+pOK2fShRZW1iUhxb1iAIbLZQldJAs
HQJjzVTWr80H708VnjdcfbiVLLVZYOtA0QpkYYwvPCu+7FPtY6eHKf1L/Gkhkacr
ZQIDAQAB
-----END PUBLIC KEY-----`
) )
var ( var (
decodedPublicKey *rsa.PublicKey decodedPublicKey *rsa.PublicKey
// decodedPrivateKey *rsa.PrivateKey
) )
// DecryptWithPrivateKey decrypts data with private key
func decryptWithPrivateKey(ciphertext []byte, priv *rsa.PrivateKey) []byte {
hash := sha256.New()
plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, priv, ciphertext, nil)
if err != nil {
panic(err)
}
return plaintext
}
func Seed() { func Seed() {
var ( var (
block *pem.Block block *pem.Block
decKey any decKey any
ok bool
err error err error
) )
@ -36,7 +83,10 @@ func Seed() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
decodedPublicKey = decKey.(*rsa.PublicKey)
decodedPublicKey, ok = decKey.(*rsa.PublicKey)
if !ok {
panic(errors.New("Invalid decodedPublicKey"))
}
log.Println("Seeding users...") log.Println("Seeding users...")
SeedUsers() SeedUsers()


+ 1
- 1
Backend/Models/Friends.go View File

@ -11,6 +11,6 @@ type Friend struct {
Base Base
UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;" json:"user_id"` UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;" json:"user_id"`
User User `json:"user"` User User `json:"user"`
FriendID []byte `gorm:"not null" json:"friend_id"` // Stored encrypted
FriendID string `gorm:"not null" json:"friend_id"` // Stored encrypted
AcceptedAt time.Time `json:"accepted_at"` AcceptedAt time.Time `json:"accepted_at"`
} }

+ 47
- 2
mobile/lib/models/friends.dart View File

@ -1,9 +1,54 @@
import 'dart:convert';
import "package:pointycastle/export.dart";
import '/utils/encryption/crypto_utils.dart';
class Friend{ class Friend{
String id; String id;
String username;
String userId;
String friendId;
String friendIdDecrypted;
String acceptedAt;
Friend({ Friend({
required this.id, required this.id,
required this.username,
required this.userId,
required this.friendId,
required this.friendIdDecrypted,
required this.acceptedAt,
}); });
factory Friend.fromJson(Map<String, dynamic> json, RSAPrivateKey privKey) {
var friendIdDecrypted = CryptoUtils.rsaDecrypt(
base64.decode(json['friend_id']),
privKey,
);
return Friend(
id: json['id'],
userId: json['user_id'],
friendId: json['friend_id'],
friendIdDecrypted: String.fromCharCodes(friendIdDecrypted),
acceptedAt: json['accepted_at'],
);
}
@override
String toString() {
return '''
id: $id
userId: $userId,
friendId: $friendId,
friendIdDecrypted: $friendIdDecrypted,
accepted_at: $acceptedAt,
''';
}
Map<String, dynamic> toMap() {
return {
'id': id,
'user_id': userId,
'friend_id': friendId,
'friend_id_decrypted': friendIdDecrypted,
'accepted_at': acceptedAt,
};
}
} }

+ 979
- 0
mobile/lib/utils/encryption/crypto_utils.dart View File

@ -0,0 +1,979 @@
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:pointycastle/asn1/object_identifiers.dart';
import 'package:pointycastle/export.dart';
import 'package:pointycastle/pointycastle.dart';
import 'package:pointycastle/src/utils.dart' as thing;
import 'package:pointycastle/ecc/ecc_fp.dart' as ecc_fp;
import './string_utils.dart';
///
/// Helper class for cryptographic operations
///
class CryptoUtils {
static const BEGIN_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----';
static const END_PRIVATE_KEY = '-----END PRIVATE KEY-----';
static const BEGIN_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----';
static const END_PUBLIC_KEY = '-----END PUBLIC KEY-----';
static const BEGIN_EC_PRIVATE_KEY = '-----BEGIN EC PRIVATE KEY-----';
static const END_EC_PRIVATE_KEY = '-----END EC PRIVATE KEY-----';
static const BEGIN_EC_PUBLIC_KEY = '-----BEGIN EC PUBLIC KEY-----';
static const END_EC_PUBLIC_KEY = '-----END EC PUBLIC KEY-----';
static const BEGIN_RSA_PRIVATE_KEY = '-----BEGIN RSA PRIVATE KEY-----';
static const END_RSA_PRIVATE_KEY = '-----END RSA PRIVATE KEY-----';
static const BEGIN_RSA_PUBLIC_KEY = '-----BEGIN RSA PUBLIC KEY-----';
static const END_RSA_PUBLIC_KEY = '-----END RSA PUBLIC KEY-----';
///
/// Converts the [RSAPublicKey.modulus] from the given [publicKey] to a [Uint8List].
///
static Uint8List rsaPublicKeyModulusToBytes(RSAPublicKey publicKey) =>
thing.encodeBigInt(publicKey.modulus);
///
/// Converts the [RSAPublicKey.exponent] from the given [publicKey] to a [Uint8List].
///
static Uint8List rsaPublicKeyExponentToBytes(RSAPublicKey publicKey) =>
thing.encodeBigInt(publicKey.exponent);
///
/// Converts the [RSAPrivateKey.modulus] from the given [privateKey] to a [Uint8List].
///
static Uint8List rsaPrivateKeyModulusToBytes(RSAPrivateKey privateKey) =>
thing.encodeBigInt(privateKey.modulus);
///
/// Converts the [RSAPrivateKey.exponent] from the given [privateKey] to a [Uint8List].
///
static Uint8List rsaPrivateKeyExponentToBytes(RSAPrivateKey privateKey) =>
thing.encodeBigInt(privateKey.exponent);
///
/// Get a SHA1 Thumbprint for the given [bytes].
///
@Deprecated('Use [getHash]')
static String getSha1ThumbprintFromBytes(Uint8List bytes) {
return getHash(bytes, algorithmName: 'SHA-1');
}
///
/// Get a SHA256 Thumbprint for the given [bytes].
///
@Deprecated('Use [getHash]')
static String getSha256ThumbprintFromBytes(Uint8List bytes) {
return getHash(bytes, algorithmName: 'SHA-256');
}
///
/// Get a MD5 Thumbprint for the given [bytes].
///
@Deprecated('Use [getHash]')
static String getMd5ThumbprintFromBytes(Uint8List bytes) {
return getHash(bytes, algorithmName: 'MD5');
}
///
/// Get a hash for the given [bytes] using the given [algorithm]
///
/// The default [algorithm] used is **SHA-256**. All supported algorihms are :
///
/// * SHA-1
/// * SHA-224
/// * SHA-256
/// * SHA-384
/// * SHA-512
/// * SHA-512/224
/// * SHA-512/256
/// * MD5
///
static String getHash(Uint8List bytes, {String algorithmName = 'SHA-256'}) {
var hash = getHashPlain(bytes, algorithmName: algorithmName);
const hexDigits = '0123456789abcdef';
var charCodes = Uint8List(hash.length * 2);
for (var i = 0, j = 0; i < hash.length; i++) {
var byte = hash[i];
charCodes[j++] = hexDigits.codeUnitAt((byte >> 4) & 0xF);
charCodes[j++] = hexDigits.codeUnitAt(byte & 0xF);
}
return String.fromCharCodes(charCodes).toUpperCase();
}
///
/// Get a hash for the given [bytes] using the given [algorithm]
///
/// The default [algorithm] used is **SHA-256**. All supported algorihms are :
///
/// * SHA-1
/// * SHA-224
/// * SHA-256
/// * SHA-384
/// * SHA-512
/// * SHA-512/224
/// * SHA-512/256
/// * MD5
///
static Uint8List getHashPlain(Uint8List bytes,
{String algorithmName = 'SHA-256'}) {
Uint8List hash;
switch (algorithmName) {
case 'SHA-1':
hash = Digest('SHA-1').process(bytes);
break;
case 'SHA-224':
hash = Digest('SHA-224').process(bytes);
break;
case 'SHA-256':
hash = Digest('SHA-256').process(bytes);
break;
case 'SHA-384':
hash = Digest('SHA-384').process(bytes);
break;
case 'SHA-512':
hash = Digest('SHA-512').process(bytes);
break;
case 'SHA-512/224':
hash = Digest('SHA-512/224').process(bytes);
break;
case 'SHA-512/256':
hash = Digest('SHA-512/256').process(bytes);
break;
case 'MD5':
hash = Digest('MD5').process(bytes);
break;
default:
throw ArgumentError('Hash not supported');
}
return hash;
}
///
/// Generates a RSA [AsymmetricKeyPair] with the given [keySize].
/// The default value for the [keySize] is 2048 bits.
///
/// The following keySize is supported:
/// * 1024
/// * 2048
/// * 4096
/// * 8192
///
static AsymmetricKeyPair<RSAPublicKey, RSAPrivateKey> generateRSAKeyPair({int keySize = 2048}) {
var keyParams =
RSAKeyGeneratorParameters(BigInt.parse('65537'), keySize, 12);
var secureRandom = _getSecureRandom();
var rngParams = ParametersWithRandom(keyParams, secureRandom);
var generator = RSAKeyGenerator();
generator.init(rngParams);
final pair = generator.generateKeyPair();
final myPublic = pair.publicKey as RSAPublicKey;
final myPrivate = pair.privateKey as RSAPrivateKey;
return AsymmetricKeyPair<RSAPublicKey, RSAPrivateKey>(myPublic, myPrivate);
}
///
/// Generates a elliptic curve [AsymmetricKeyPair].
///
/// The default curve is **prime256v1**
///
/// The following curves are supported:
///
/// * brainpoolp160r1
/// * brainpoolp160t1
/// * brainpoolp192r1
/// * brainpoolp192t1
/// * brainpoolp224r1
/// * brainpoolp224t1
/// * brainpoolp256r1
/// * brainpoolp256t1
/// * brainpoolp320r1
/// * brainpoolp320t1
/// * brainpoolp384r1
/// * brainpoolp384t1
/// * brainpoolp512r1
/// * brainpoolp512t1
/// * GostR3410-2001-CryptoPro-A
/// * GostR3410-2001-CryptoPro-B
/// * GostR3410-2001-CryptoPro-C
/// * GostR3410-2001-CryptoPro-XchA
/// * GostR3410-2001-CryptoPro-XchB
/// * prime192v1
/// * prime192v2
/// * prime192v3
/// * prime239v1
/// * prime239v2
/// * prime239v3
/// * prime256v1
/// * secp112r1
/// * secp112r2
/// * secp128r1
/// * secp128r2
/// * secp160k1
/// * secp160r1
/// * secp160r2
/// * secp192k1
/// * secp192r1
/// * secp224k1
/// * secp224r1
/// * secp256k1
/// * secp256r1
/// * secp384r1
/// * secp521r1
///
static AsymmetricKeyPair generateEcKeyPair({String curve = 'prime256v1'}) {
var ecDomainParameters = ECDomainParameters(curve);
var keyParams = ECKeyGeneratorParameters(ecDomainParameters);
var secureRandom = _getSecureRandom();
var rngParams = ParametersWithRandom(keyParams, secureRandom);
var generator = ECKeyGenerator();
generator.init(rngParams);
return generator.generateKeyPair();
}
///
/// Generates a secure [FortunaRandom]
///
static SecureRandom _getSecureRandom() {
var secureRandom = FortunaRandom();
var random = Random.secure();
var seeds = <int>[];
for (var i = 0; i < 32; i++) {
seeds.add(random.nextInt(255));
}
secureRandom.seed(KeyParameter(Uint8List.fromList(seeds)));
return secureRandom;
}
///
/// Enode the given [publicKey] to PEM format using the PKCS#8 standard.
///
static String encodeRSAPublicKeyToPem(RSAPublicKey publicKey) {
var algorithmSeq = ASN1Sequence();
var paramsAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0]));
algorithmSeq.add(ASN1ObjectIdentifier.fromName('rsaEncryption'));
algorithmSeq.add(paramsAsn1Obj);
var publicKeySeq = ASN1Sequence();
publicKeySeq.add(ASN1Integer(publicKey.modulus));
publicKeySeq.add(ASN1Integer(publicKey.exponent));
var publicKeySeqBitString =
ASN1BitString(stringValues: Uint8List.fromList(publicKeySeq.encode()));
var topLevelSeq = ASN1Sequence();
topLevelSeq.add(algorithmSeq);
topLevelSeq.add(publicKeySeqBitString);
var dataBase64 = base64.encode(topLevelSeq.encode());
var chunks = StringUtils.chunk(dataBase64, 64);
return '$BEGIN_PUBLIC_KEY\n${chunks.join('\n')}\n$END_PUBLIC_KEY';
}
///
/// Enode the given [rsaPublicKey] to PEM format using the PKCS#1 standard.
///
/// The ASN1 structure is decripted at <https://tools.ietf.org/html/rfc8017#page-53>.
///
/// ```
/// RSAPublicKey ::= SEQUENCE {
/// modulus INTEGER, -- n
/// publicExponent INTEGER -- e
/// }
/// ```
///
static String encodeRSAPublicKeyToPemPkcs1(RSAPublicKey rsaPublicKey) {
var topLevelSeq = ASN1Sequence();
topLevelSeq.add(ASN1Integer(rsaPublicKey.modulus));
topLevelSeq.add(ASN1Integer(rsaPublicKey.exponent));
var dataBase64 = base64.encode(topLevelSeq.encode());
var chunks = StringUtils.chunk(dataBase64, 64);
return '$BEGIN_RSA_PUBLIC_KEY\n${chunks.join('\n')}\n$END_RSA_PUBLIC_KEY';
}
///
/// Enode the given [rsaPrivateKey] to PEM format using the PKCS#1 standard.
///
/// The ASN1 structure is decripted at <https://tools.ietf.org/html/rfc8017#page-54>.
///
/// ```
/// RSAPrivateKey ::= SEQUENCE {
/// version Version,
/// modulus INTEGER, -- n
/// publicExponent INTEGER, -- e
/// privateExponent INTEGER, -- d
/// prime1 INTEGER, -- p
/// prime2 INTEGER, -- q
/// exponent1 INTEGER, -- d mod (p-1)
/// exponent2 INTEGER, -- d mod (q-1)
/// coefficient INTEGER, -- (inverse of q) mod p
/// otherPrimeInfos OtherPrimeInfos OPTIONAL
/// }
/// ```
static String encodeRSAPrivateKeyToPemPkcs1(RSAPrivateKey rsaPrivateKey) {
var version = ASN1Integer(BigInt.from(0));
var modulus = ASN1Integer(rsaPrivateKey.n);
var publicExponent = ASN1Integer(BigInt.parse('65537'));
var privateExponent = ASN1Integer(rsaPrivateKey.privateExponent);
var p = ASN1Integer(rsaPrivateKey.p);
var q = ASN1Integer(rsaPrivateKey.q);
var dP =
rsaPrivateKey.privateExponent! % (rsaPrivateKey.p! - BigInt.from(1));
var exp1 = ASN1Integer(dP);
var dQ =
rsaPrivateKey.privateExponent! % (rsaPrivateKey.q! - BigInt.from(1));
var exp2 = ASN1Integer(dQ);
var iQ = rsaPrivateKey.q!.modInverse(rsaPrivateKey.p!);
var co = ASN1Integer(iQ);
var topLevelSeq = ASN1Sequence();
topLevelSeq.add(version);
topLevelSeq.add(modulus);
topLevelSeq.add(publicExponent);
topLevelSeq.add(privateExponent);
topLevelSeq.add(p);
topLevelSeq.add(q);
topLevelSeq.add(exp1);
topLevelSeq.add(exp2);
topLevelSeq.add(co);
var dataBase64 = base64.encode(topLevelSeq.encode());
var chunks = StringUtils.chunk(dataBase64, 64);
return '$BEGIN_RSA_PRIVATE_KEY\n${chunks.join('\n')}\n$END_RSA_PRIVATE_KEY';
}
///
/// Enode the given [rsaPrivateKey] to PEM format using the PKCS#8 standard.
///
/// The ASN1 structure is decripted at <https://tools.ietf.org/html/rfc5208>.
/// ```
/// PrivateKeyInfo ::= SEQUENCE {
/// version Version,
/// algorithm AlgorithmIdentifier,
/// PrivateKey BIT STRING
/// }
/// ```
///
static String encodeRSAPrivateKeyToPem(RSAPrivateKey rsaPrivateKey) {
var version = ASN1Integer(BigInt.from(0));
var algorithmSeq = ASN1Sequence();
var algorithmAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList(
[0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1]));
var paramsAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0]));
algorithmSeq.add(algorithmAsn1Obj);
algorithmSeq.add(paramsAsn1Obj);
var privateKeySeq = ASN1Sequence();
var modulus = ASN1Integer(rsaPrivateKey.n);
var publicExponent = ASN1Integer(BigInt.parse('65537'));
var privateExponent = ASN1Integer(rsaPrivateKey.privateExponent);
var p = ASN1Integer(rsaPrivateKey.p);
var q = ASN1Integer(rsaPrivateKey.q);
var dP =
rsaPrivateKey.privateExponent! % (rsaPrivateKey.p! - BigInt.from(1));
var exp1 = ASN1Integer(dP);
var dQ =
rsaPrivateKey.privateExponent! % (rsaPrivateKey.q! - BigInt.from(1));
var exp2 = ASN1Integer(dQ);
var iQ = rsaPrivateKey.q!.modInverse(rsaPrivateKey.p!);
var co = ASN1Integer(iQ);
privateKeySeq.add(version);
privateKeySeq.add(modulus);
privateKeySeq.add(publicExponent);
privateKeySeq.add(privateExponent);
privateKeySeq.add(p);
privateKeySeq.add(q);
privateKeySeq.add(exp1);
privateKeySeq.add(exp2);
privateKeySeq.add(co);
var publicKeySeqOctetString =
ASN1OctetString(octets: Uint8List.fromList(privateKeySeq.encode()));
var topLevelSeq = ASN1Sequence();
topLevelSeq.add(version);
topLevelSeq.add(algorithmSeq);
topLevelSeq.add(publicKeySeqOctetString);
var dataBase64 = base64.encode(topLevelSeq.encode());
var chunks = StringUtils.chunk(dataBase64, 64);
return '$BEGIN_PRIVATE_KEY\n${chunks.join('\n')}\n$END_PRIVATE_KEY';
}
///
/// Decode a [RSAPrivateKey] from the given [pem] String.
///
static RSAPrivateKey rsaPrivateKeyFromPem(String pem) {
var bytes = getBytesFromPEMString(pem);
return rsaPrivateKeyFromDERBytes(bytes);
}
///
/// Decode the given [bytes] into an [RSAPrivateKey].
///
static RSAPrivateKey rsaPrivateKeyFromDERBytes(Uint8List bytes) {
var asn1Parser = ASN1Parser(bytes);
var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
//ASN1Object version = topLevelSeq.elements[0];
//ASN1Object algorithm = topLevelSeq.elements[1];
var privateKey = topLevelSeq.elements![2];
asn1Parser = ASN1Parser(privateKey.valueBytes);
var pkSeq = asn1Parser.nextObject() as ASN1Sequence;
var modulus = pkSeq.elements![1] as ASN1Integer;
//ASN1Integer publicExponent = pkSeq.elements[2] as ASN1Integer;
var privateExponent = pkSeq.elements![3] as ASN1Integer;
var p = pkSeq.elements![4] as ASN1Integer;
var q = pkSeq.elements![5] as ASN1Integer;
//ASN1Integer exp1 = pkSeq.elements[6] as ASN1Integer;
//ASN1Integer exp2 = pkSeq.elements[7] as ASN1Integer;
//ASN1Integer co = pkSeq.elements[8] as ASN1Integer;
var rsaPrivateKey = RSAPrivateKey(
modulus.integer!, privateExponent.integer!, p.integer, q.integer);
return rsaPrivateKey;
}
///
/// Decode a [RSAPrivateKey] from the given [pem] string formated in the pkcs1 standard.
///
static RSAPrivateKey rsaPrivateKeyFromPemPkcs1(String pem) {
var bytes = getBytesFromPEMString(pem);
return rsaPrivateKeyFromDERBytesPkcs1(bytes);
}
///
/// Decode the given [bytes] into an [RSAPrivateKey].
///
/// The [bytes] need to follow the the pkcs1 standard
///
static RSAPrivateKey rsaPrivateKeyFromDERBytesPkcs1(Uint8List bytes) {
var asn1Parser = ASN1Parser(bytes);
var pkSeq = asn1Parser.nextObject() as ASN1Sequence;
var modulus = pkSeq.elements![1] as ASN1Integer;
//ASN1Integer publicExponent = pkSeq.elements[2] as ASN1Integer;
var privateExponent = pkSeq.elements![3] as ASN1Integer;
var p = pkSeq.elements![4] as ASN1Integer;
var q = pkSeq.elements![5] as ASN1Integer;
//ASN1Integer exp1 = pkSeq.elements[6] as ASN1Integer;
//ASN1Integer exp2 = pkSeq.elements[7] as ASN1Integer;
//ASN1Integer co = pkSeq.elements[8] as ASN1Integer;
var rsaPrivateKey = RSAPrivateKey(
modulus.integer!, privateExponent.integer!, p.integer, q.integer);
return rsaPrivateKey;
}
///
/// Helper function for decoding the base64 in [pem].
///
/// Throws an ArgumentError if the given [pem] is not sourounded by begin marker -----BEGIN and
/// endmarker -----END or the [pem] consists of less than two lines.
///
/// The PEM header check can be skipped by setting the optional paramter [checkHeader] to false.
///
static Uint8List getBytesFromPEMString(String pem,
{bool checkHeader = true}) {
var lines = LineSplitter.split(pem)
.map((line) => line.trim())
.where((line) => line.isNotEmpty)
.toList();
var base64;
if (checkHeader) {
if (lines.length < 2 ||
!lines.first.startsWith('-----BEGIN') ||
!lines.last.startsWith('-----END')) {
throw ArgumentError('The given string does not have the correct '
'begin/end markers expected in a PEM file.');
}
base64 = lines.sublist(1, lines.length - 1).join('');
} else {
base64 = lines.join('');
}
return Uint8List.fromList(base64Decode(base64));
}
///
/// Decode a [RSAPublicKey] from the given [pem] String.
///
static RSAPublicKey rsaPublicKeyFromPem(String pem) {
var bytes = CryptoUtils.getBytesFromPEMString(pem);
return rsaPublicKeyFromDERBytes(bytes);
}
///
/// Decode the given [bytes] into an [RSAPublicKey].
///
static RSAPublicKey rsaPublicKeyFromDERBytes(Uint8List bytes) {
var asn1Parser = ASN1Parser(bytes);
var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
var publicKeySeq;
if (topLevelSeq.elements![1].runtimeType == ASN1BitString) {
var publicKeyBitString = topLevelSeq.elements![1] as ASN1BitString;
var publicKeyAsn =
ASN1Parser(publicKeyBitString.stringValues as Uint8List?);
publicKeySeq = publicKeyAsn.nextObject() as ASN1Sequence;
} else {
publicKeySeq = topLevelSeq;
}
var modulus = publicKeySeq.elements![0] as ASN1Integer;
var exponent = publicKeySeq.elements![1] as ASN1Integer;
var rsaPublicKey = RSAPublicKey(modulus.integer!, exponent.integer!);
return rsaPublicKey;
}
///
/// Decode a [RSAPublicKey] from the given [pem] string formated in the pkcs1 standard.
///
static RSAPublicKey rsaPublicKeyFromPemPkcs1(String pem) {
var bytes = CryptoUtils.getBytesFromPEMString(pem);
return rsaPublicKeyFromDERBytesPkcs1(bytes);
}
///
/// Decode the given [bytes] into an [RSAPublicKey].
///
/// The [bytes] need to follow the the pkcs1 standard
///
static RSAPublicKey rsaPublicKeyFromDERBytesPkcs1(Uint8List bytes) {
var publicKeyAsn = ASN1Parser(bytes);
var publicKeySeq = publicKeyAsn.nextObject() as ASN1Sequence;
var modulus = publicKeySeq.elements![0] as ASN1Integer;
var exponent = publicKeySeq.elements![1] as ASN1Integer;
var rsaPublicKey = RSAPublicKey(modulus.integer!, exponent.integer!);
return rsaPublicKey;
}
///
/// Enode the given elliptic curve [publicKey] to PEM format.
///
/// This is descripted in <https://tools.ietf.org/html/rfc5915>
///
/// ```ASN1
/// ECPrivateKey ::= SEQUENCE {
/// version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
/// privateKey OCTET STRING
/// parameters [0] ECParameters {{ NamedCurve }} OPTIONAL
/// publicKey [1] BIT STRING OPTIONAL
/// }
///
/// ```
///
/// As descripted in the mentioned RFC, all optional values will always be set.
///
static String encodeEcPrivateKeyToPem(ECPrivateKey ecPrivateKey) {
var outer = ASN1Sequence();
var version = ASN1Integer(BigInt.from(1));
var privateKeyAsBytes = thing.encodeBigInt(ecPrivateKey.d);
var privateKey = ASN1OctetString(octets: privateKeyAsBytes);
var choice = ASN1Sequence(tag: 0xA0);
choice.add(
ASN1ObjectIdentifier.fromName(ecPrivateKey.parameters!.domainName));
var publicKey = ASN1Sequence(tag: 0xA1);
var subjectPublicKey = ASN1BitString(
stringValues: ecPrivateKey.parameters!.G.getEncoded(false));
publicKey.add(subjectPublicKey);
outer.add(version);
outer.add(privateKey);
outer.add(choice);
outer.add(publicKey);
var dataBase64 = base64.encode(outer.encode());
var chunks = StringUtils.chunk(dataBase64, 64);
return '$BEGIN_EC_PRIVATE_KEY\n${chunks.join('\n')}\n$END_EC_PRIVATE_KEY';
}
///
/// Enode the given elliptic curve [publicKey] to PEM format.
///
/// This is descripted in <https://tools.ietf.org/html/rfc5480>
///
/// ```ASN1
/// SubjectPublicKeyInfo ::= SEQUENCE {
/// algorithm AlgorithmIdentifier,
/// subjectPublicKey BIT STRING
/// }
/// ```
///
static String encodeEcPublicKeyToPem(ECPublicKey publicKey) {
var outer = ASN1Sequence();
var algorithm = ASN1Sequence();
algorithm.add(ASN1ObjectIdentifier.fromName('ecPublicKey'));
algorithm.add(ASN1ObjectIdentifier.fromName('prime256v1'));
var encodedBytes = publicKey.Q!.getEncoded(false);
var subjectPublicKey = ASN1BitString(stringValues: encodedBytes);
outer.add(algorithm);
outer.add(subjectPublicKey);
var dataBase64 = base64.encode(outer.encode());
var chunks = StringUtils.chunk(dataBase64, 64);
return '$BEGIN_EC_PUBLIC_KEY\n${chunks.join('\n')}\n$END_EC_PUBLIC_KEY';
}
///
/// Decode a [ECPublicKey] from the given [pem] String.
///
/// Throws an ArgumentError if the given string [pem] is null or empty.
///
static ECPublicKey ecPublicKeyFromPem(String pem) {
if (pem.isEmpty) {
throw ArgumentError('Argument must not be null.');
}
var bytes = CryptoUtils.getBytesFromPEMString(pem);
return ecPublicKeyFromDerBytes(bytes);
}
///
/// Decode a [ECPrivateKey] from the given [pem] String.
///
/// Throws an ArgumentError if the given string [pem] is null or empty.
///
static ECPrivateKey ecPrivateKeyFromPem(String pem) {
if (pem.isEmpty) {
throw ArgumentError('Argument must not be null.');
}
var bytes = CryptoUtils.getBytesFromPEMString(pem);
return ecPrivateKeyFromDerBytes(
bytes,
pkcs8: pem.startsWith(BEGIN_PRIVATE_KEY),
);
}
///
/// Decode the given [bytes] into an [ECPrivateKey].
///
/// [pkcs8] defines the ASN1 format of the given [bytes]. The default is false, so SEC1 is assumed.
///
/// Supports SEC1 (<https://tools.ietf.org/html/rfc5915>) and PKCS8 (<https://datatracker.ietf.org/doc/html/rfc5208>)
///
static ECPrivateKey ecPrivateKeyFromDerBytes(Uint8List bytes,
{bool pkcs8 = false}) {
var asn1Parser = ASN1Parser(bytes);
var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
var curveName;
var x;
if (pkcs8) {
// Parse the PKCS8 format
var innerSeq = topLevelSeq.elements!.elementAt(1) as ASN1Sequence;
var b2 = innerSeq.elements!.elementAt(1) as ASN1ObjectIdentifier;
var b2Data = b2.objectIdentifierAsString;
var b2Curvedata = ObjectIdentifiers.getIdentifierByIdentifier(b2Data);
if (b2Curvedata != null) {
curveName = b2Curvedata['readableName'];
}
var octetString = topLevelSeq.elements!.elementAt(2) as ASN1OctetString;
asn1Parser = ASN1Parser(octetString.valueBytes);
var octetStringSeq = asn1Parser.nextObject() as ASN1Sequence;
var octetStringKeyData =
octetStringSeq.elements!.elementAt(1) as ASN1OctetString;
x = octetStringKeyData.valueBytes!;
} else {
// Parse the SEC1 format
var privateKeyAsOctetString =
topLevelSeq.elements!.elementAt(1) as ASN1OctetString;
var choice = topLevelSeq.elements!.elementAt(2);
var s = ASN1Sequence();
var parser = ASN1Parser(choice.valueBytes);
while (parser.hasNext()) {
s.add(parser.nextObject());
}
var curveNameOi = s.elements!.elementAt(0) as ASN1ObjectIdentifier;
var data = ObjectIdentifiers.getIdentifierByIdentifier(
curveNameOi.objectIdentifierAsString);
if (data != null) {
curveName = data['readableName'];
}
x = privateKeyAsOctetString.valueBytes!;
}
return ECPrivateKey(thing.decodeBigInt(x), ECDomainParameters(curveName));
}
///
/// Decode the given [bytes] into an [ECPublicKey].
///
static ECPublicKey ecPublicKeyFromDerBytes(Uint8List bytes) {
if (bytes.elementAt(0) == 0) {
bytes = bytes.sublist(1);
}
var asn1Parser = ASN1Parser(bytes);
var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
var algorithmIdentifierSequence = topLevelSeq.elements![0] as ASN1Sequence;
var curveNameOi = algorithmIdentifierSequence.elements!.elementAt(1)
as ASN1ObjectIdentifier;
var curveName;
var data = ObjectIdentifiers.getIdentifierByIdentifier(
curveNameOi.objectIdentifierAsString);
if (data != null) {
curveName = data['readableName'];
}
var subjectPublicKey = topLevelSeq.elements![1] as ASN1BitString;
var compressed = false;
var pubBytes = subjectPublicKey.valueBytes!;
if (pubBytes.elementAt(0) == 0) {
pubBytes = pubBytes.sublist(1);
}
// Looks good so far!
var firstByte = pubBytes.elementAt(0);
if (firstByte != 4) {
compressed = true;
}
var x = pubBytes.sublist(1, (pubBytes.length / 2).round());
var y = pubBytes.sublist(1 + x.length, pubBytes.length);
var params = ECDomainParameters(curveName);
var bigX = thing.decodeBigIntWithSign(1, x);
var bigY = thing.decodeBigIntWithSign(1, y);
var pubKey = ECPublicKey(
ecc_fp.ECPoint(
params.curve as ecc_fp.ECCurve,
params.curve.fromBigInteger(bigX) as ecc_fp.ECFieldElement?,
params.curve.fromBigInteger(bigY) as ecc_fp.ECFieldElement?,
compressed),
params);
return pubKey;
}
///
/// Encrypt the given [message] using the given RSA [publicKey].
///
static Uint8List rsaEncrypt(Uint8List message, RSAPublicKey publicKey) {
var cipher = OAEPEncoding.withSHA256(RSAEngine())
..init(true, PublicKeyParameter<RSAPublicKey>(publicKey));
return _processInBlocks(cipher, message);
}
///
/// Decrypt the given [cipherMessage] using the given RSA [privateKey].
///
static Uint8List rsaDecrypt(Uint8List cipherMessage, RSAPrivateKey privateKey) {
var cipher = OAEPEncoding.withSHA256(RSAEngine())
..init(false, PrivateKeyParameter<RSAPrivateKey>(privateKey));
return _processInBlocks(cipher, cipherMessage);
}
static Uint8List _processInBlocks(AsymmetricBlockCipher engine, Uint8List input) {
final numBlocks = input.length ~/ engine.inputBlockSize +
((input.length % engine.inputBlockSize != 0) ? 1 : 0);
final output = Uint8List(numBlocks * engine.outputBlockSize);
var inputOffset = 0;
var outputOffset = 0;
while (inputOffset < input.length) {
final chunkSize = (inputOffset + engine.inputBlockSize <= input.length)
? engine.inputBlockSize
: input.length - inputOffset;
outputOffset += engine.processBlock(
input, inputOffset, chunkSize, output, outputOffset);
inputOffset += chunkSize;
}
return (output.length == outputOffset)
? output
: output.sublist(0, outputOffset);
}
///
/// Signing the given [dataToSign] with the given [privateKey].
///
/// The default [algorithm] used is **SHA-256/RSA**. All supported algorihms are :
///
/// * MD2/RSA
/// * MD4/RSA
/// * MD5/RSA
/// * RIPEMD-128/RSA
/// * RIPEMD-160/RSA
/// * RIPEMD-256/RSA
/// * SHA-1/RSA
/// * SHA-224/RSA
/// * SHA-256/RSA
/// * SHA-384/RSA
/// * SHA-512/RSA
///
static Uint8List rsaSign(RSAPrivateKey privateKey, Uint8List dataToSign,
{String algorithmName = 'SHA-256/RSA'}) {
var signer = Signer(algorithmName) as RSASigner;
signer.init(true, PrivateKeyParameter<RSAPrivateKey>(privateKey));
var sig = signer.generateSignature(dataToSign);
return sig.bytes;
}
///
/// Verifying the given [signedData] with the given [publicKey] and the given [signature].
/// Will return **true** if the given [signature] matches the [signedData].
///
/// The default [algorithm] used is **SHA-256/RSA**. All supported algorihms are :
///
/// * MD2/RSA
/// * MD4/RSA
/// * MD5/RSA
/// * RIPEMD-128/RSA
/// * RIPEMD-160/RSA
/// * RIPEMD-256/RSA
/// * SHA-1/RSA
/// * SHA-224/RSA
/// * SHA-256/RSA
/// * SHA-384/RSA
/// * SHA-512/RSA
///
static bool rsaVerify(
RSAPublicKey publicKey, Uint8List signedData, Uint8List signature,
{String algorithm = 'SHA-256/RSA'}) {
final sig = RSASignature(signature);
final verifier = Signer(algorithm);
verifier.init(false, PublicKeyParameter<RSAPublicKey>(publicKey));
try {
return verifier.verifySignature(signedData, sig);
} on ArgumentError {
return false;
}
}
///
/// Signing the given [dataToSign] with the given [privateKey].
///
/// The default [algorithm] used is **SHA-1/ECDSA**. All supported algorihms are :
///
/// * SHA-1/ECDSA
/// * SHA-224/ECDSA
/// * SHA-256/ECDSA
/// * SHA-384/ECDSA
/// * SHA-512/ECDSA
/// * SHA-1/DET-ECDSA
/// * SHA-224/DET-ECDSA
/// * SHA-256/DET-ECDSA
/// * SHA-384/DET-ECDSA
/// * SHA-512/DET-ECDSA
///
static ECSignature ecSign(ECPrivateKey privateKey, Uint8List dataToSign,
{String algorithmName = 'SHA-1/ECDSA'}) {
var signer = Signer(algorithmName) as ECDSASigner;
var params = ParametersWithRandom(
PrivateKeyParameter<ECPrivateKey>(privateKey), _getSecureRandom());
signer.init(true, params);
var sig = signer.generateSignature(dataToSign) as ECSignature;
return sig;
}
///
/// Verifying the given [signedData] with the given [publicKey] and the given [signature].
/// Will return **true** if the given [signature] matches the [signedData].
///
/// The default [algorithm] used is **SHA-1/ECDSA**. All supported algorihms are :
///
/// * SHA-1/ECDSA
/// * SHA-224/ECDSA
/// * SHA-256/ECDSA
/// * SHA-384/ECDSA
/// * SHA-512/ECDSA
/// * SHA-1/DET-ECDSA
/// * SHA-224/DET-ECDSA
/// * SHA-256/DET-ECDSA
/// * SHA-384/DET-ECDSA
/// * SHA-512/DET-ECDSA
///
static bool ecVerify(
ECPublicKey publicKey, Uint8List signedData, ECSignature signature,
{String algorithm = 'SHA-1/ECDSA'}) {
final verifier = Signer(algorithm) as ECDSASigner;
verifier.init(false, PublicKeyParameter<ECPublicKey>(publicKey));
try {
return verifier.verifySignature(signedData, signature);
} on ArgumentError {
return false;
}
}
///
/// Returns the modulus of the given [pem] that represents an RSA private key.
///
/// This equals the following openssl command:
/// ```
/// openssl rsa -noout -modulus -in FILE.key
/// ```
///
static BigInt getModulusFromRSAPrivateKeyPem(String pem) {
RSAPrivateKey privateKey;
switch (_getPrivateKeyType(pem)) {
case 'RSA':
privateKey = rsaPrivateKeyFromPem(pem);
return privateKey.modulus!;
case 'RSA_PKCS1':
privateKey = rsaPrivateKeyFromPemPkcs1(pem);
return privateKey.modulus!;
case 'ECC':
throw ArgumentError('ECC private key not supported.');
default:
privateKey = rsaPrivateKeyFromPem(pem);
return privateKey.modulus!;
}
}
///
/// Returns the private key type of the given [pem]
///
static String _getPrivateKeyType(String pem) {
if (pem.startsWith(BEGIN_RSA_PRIVATE_KEY)) {
return 'RSA_PKCS1';
} else if (pem.startsWith(BEGIN_PRIVATE_KEY)) {
return 'RSA';
} else if (pem.startsWith(BEGIN_EC_PRIVATE_KEY)) {
return 'ECC';
}
return 'RSA';
}
}

+ 16
- 13
mobile/lib/utils/encryption/rsa_key_helper.dart View File

@ -1,7 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:pointycastle/src/platform_check/platform_check.dart';
import "package:pointycastle/export.dart"; import "package:pointycastle/export.dart";
import "package:asn1lib/asn1lib.dart"; import "package:asn1lib/asn1lib.dart";
@ -19,16 +18,18 @@ print(String.fromCharCodes(plainText));
List<int> decodePEM(String pem) { List<int> decodePEM(String pem) {
var startsWith = [ var startsWith = [
"-----BEGIN PUBLIC KEY-----",
"-----BEGIN PRIVATE KEY-----",
"-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: React-Native-OpenPGP.js 0.1\r\nComment: http://openpgpjs.org\r\n\r\n",
"-----BEGIN PGP PRIVATE KEY BLOCK-----\r\nVersion: React-Native-OpenPGP.js 0.1\r\nComment: http://openpgpjs.org\r\n\r\n",
'-----BEGIN PUBLIC KEY-----',
'-----BEGIN PUBLIC KEY-----',
'\n-----BEGIN PUBLIC KEY-----',
'-----BEGIN PRIVATE KEY-----',
'-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: React-Native-OpenPGP.js 0.1\r\nComment: http://openpgpjs.org\r\n\r\n',
'-----BEGIN PGP PRIVATE KEY BLOCK-----\r\nVersion: React-Native-OpenPGP.js 0.1\r\nComment: http://openpgpjs.org\r\n\r\n',
]; ];
var endsWith = [ var endsWith = [
"-----END PUBLIC KEY-----",
"-----END PRIVATE KEY-----",
"-----END PGP PUBLIC KEY BLOCK-----",
"-----END PGP PRIVATE KEY BLOCK-----",
'-----END PUBLIC KEY-----',
'-----END PRIVATE KEY-----',
'-----END PGP PUBLIC KEY BLOCK-----',
'-----END PGP PRIVATE KEY BLOCK-----',
]; ];
bool isOpenPgp = pem.contains('BEGIN PGP'); bool isOpenPgp = pem.contains('BEGIN PGP');
@ -183,10 +184,12 @@ class RsaKeyHelper {
// Encode RSA private key to pem format // Encode RSA private key to pem format
static encodePrivateKeyToPem(RSAPrivateKey privateKey) { static encodePrivateKeyToPem(RSAPrivateKey privateKey) {
var version = ASN1Integer(BigInt.from(0));
var version = ASN1Integer(BigInt.zero);
var algorithmSeq = ASN1Sequence(); var algorithmSeq = ASN1Sequence();
var algorithmAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList([0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1]));
var algorithmAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList([
0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1
]));
var paramsAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0])); var paramsAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0]));
algorithmSeq.add(algorithmAsn1Obj); algorithmSeq.add(algorithmAsn1Obj);
algorithmSeq.add(paramsAsn1Obj); algorithmSeq.add(paramsAsn1Obj);
@ -197,9 +200,9 @@ class RsaKeyHelper {
var privateExponent = ASN1Integer(privateKey.privateExponent!); var privateExponent = ASN1Integer(privateKey.privateExponent!);
var p = ASN1Integer(privateKey.p!); var p = ASN1Integer(privateKey.p!);
var q = ASN1Integer(privateKey.q!); var q = ASN1Integer(privateKey.q!);
var dP = privateKey.privateExponent! % (privateKey.p! - BigInt.from(1));
var dP = privateKey.privateExponent! % (privateKey.p! - BigInt.one);
var exp1 = ASN1Integer(dP); var exp1 = ASN1Integer(dP);
var dQ = privateKey.privateExponent! % (privateKey.q! - BigInt.from(1));
var dQ = privateKey.privateExponent! % (privateKey.q! - BigInt.one);
var exp2 = ASN1Integer(dQ); var exp2 = ASN1Integer(dQ);
var iQ = privateKey.q?.modInverse(privateKey.p!); var iQ = privateKey.q?.modInverse(privateKey.p!);
var co = ASN1Integer(iQ!); var co = ASN1Integer(iQ!);


+ 463
- 0
mobile/lib/utils/encryption/string_utils.dart View File

@ -0,0 +1,463 @@
import 'dart:convert';
import 'dart:math';
///
/// Helper class for String operations
///
class StringUtils {
static AsciiCodec asciiCodec = AsciiCodec();
///
/// Returns the given string or the default string if the given string is null
///
static String defaultString(String? str, {String defaultStr = ''}) {
return str ?? defaultStr;
}
///
/// Checks if the given String [s] is null or empty
///
static bool isNullOrEmpty(String? s) =>
(s == null || s.isEmpty) ? true : false;
///
/// Checks if the given String [s] is not null or empty
///
static bool isNotNullOrEmpty(String? s) => !isNullOrEmpty(s);
///
/// Transfers the given String [s] from camcelCase to upperCaseUnderscore
/// Example : helloWorld => HELLO_WORLD
///
static String camelCaseToUpperUnderscore(String s) {
var sb = StringBuffer();
var first = true;
s.runes.forEach((int rune) {
var char = String.fromCharCode(rune);
if (isUpperCase(char) && !first) {
sb.write('_');
sb.write(char.toUpperCase());
} else {
first = false;
sb.write(char.toUpperCase());
}
});
return sb.toString();
}
///
/// Transfers the given String [s] from camcelCase to lowerCaseUnderscore
/// Example : helloWorld => hello_world
///
static String camelCaseToLowerUnderscore(String s) {
var sb = StringBuffer();
var first = true;
s.runes.forEach((int rune) {
var char = String.fromCharCode(rune);
if (isUpperCase(char) && !first) {
if (char != '_') {
sb.write('_');
}
sb.write(char.toLowerCase());
} else {
first = false;
sb.write(char.toLowerCase());
}
});
return sb.toString();
}
///
/// Checks if the given string [s] is lower case
///
static bool isLowerCase(String s) {
return s == s.toLowerCase();
}
///
/// Checks if the given string [s] is upper case
///
static bool isUpperCase(String s) {
return s == s.toUpperCase();
}
///
/// Checks if the given string [s] contains only ascii chars
///
static bool isAscii(String s) {
try {
asciiCodec.decode(s.codeUnits);
} catch (e) {
return false;
}
return true;
}
///
/// Capitalize the given string [s]. If [allWords] is set to true, it will capitalize all words within the given string [s].
///
/// The string [s] is there fore splitted by " " (space).
///
/// Example :
///
/// * [s] = "world" => World
/// * [s] = "WORLD" => World
/// * [s] = "the quick lazy fox" => The quick lazy fox
/// * [s] = "the quick lazy fox" and [allWords] = true => The Quick Lazy Fox
///
static String capitalize(String s, {bool allWords = false}) {
if (s.isEmpty) {
return '';
}
s = s.trim();
if (allWords) {
var words = s.split(' ');
var capitalized = [];
for (var w in words) {
capitalized.add(capitalize(w));
}
return capitalized.join(' ');
} else {
return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
}
}
///
/// Reverse the given string [s]
/// Example : hello => olleh
///
static String reverse(String s) {
return String.fromCharCodes(s.runes.toList().reversed);
}
///
/// Counts how offen the given [char] apears in the given string [s].
/// The value [caseSensitive] controlls whether it should only look for the given [char]
/// or also the equivalent lower/upper case version.
/// Example: Hello and char l => 2
///
static int countChars(String s, String char, {bool caseSensitive = true}) {
var count = 0;
s.codeUnits.toList().forEach((i) {
if (caseSensitive) {
if (i == char.runes.first) {
count++;
}
} else {
if (i == char.toLowerCase().runes.first ||
i == char.toUpperCase().runes.first) {
count++;
}
}
});
return count;
}
///
/// Checks if the given string [s] is a digit.
///
/// Will return false if the given string [s] is empty.
///
static bool isDigit(String s) {
if (s.isEmpty) {
return false;
}
if (s.length > 1) {
for (var r in s.runes) {
if (r ^ 0x30 > 9) {
return false;
}
}
return true;
} else {
return s.runes.first ^ 0x30 <= 9;
}
}
///
/// Compares the given strings [a] and [b].
///
static bool equalsIgnoreCase(String a, String b) =>
a.toLowerCase() == b.toLowerCase();
///
/// Checks if the given [list] contains the string [s]
///
static bool inList(String s, List<String> list, {bool ignoreCase = false}) {
for (var l in list) {
if (ignoreCase) {
if (equalsIgnoreCase(s, l)) {
return true;
}
} else {
if (s == l) {
return true;
}
}
}
return false;
}
///
/// Checks if the given string [s] is a palindrome
/// Example :
/// aha => true
/// hello => false
///
static bool isPalindrome(String s) {
for (var i = 0; i < s.length / 2; i++) {
if (s[i] != s[s.length - 1 - i]) return false;
}
return true;
}
///
/// Replaces chars of the given String [s] with [replace].
///
/// The default value of [replace] is *.
/// [begin] determines the start of the 'replacing'. If [begin] is null, it starts from index 0.
/// [end] defines the end of the 'replacing'. If [end] is null, it ends at [s] length divided by 2.
/// If [s] is empty or consists of only 1 char, the method returns null.
///
/// Example :
/// 1234567890 => *****67890
/// 1234567890 with begin 2 and end 6 => 12****7890
/// 1234567890 with begin 1 => 1****67890
///
static String? hidePartial(String s,
{int begin = 0, int? end, String replace = '*'}) {
var buffer = StringBuffer();
if (s.length <= 1) {
return null;
}
if (end == null) {
end = (s.length / 2).round();
} else {
if (end > s.length) {
end = s.length;
}
}
for (var i = 0; i < s.length; i++) {
if (i >= end) {
buffer.write(String.fromCharCode(s.runes.elementAt(i)));
continue;
}
if (i >= begin) {
buffer.write(replace);
continue;
}
buffer.write(String.fromCharCode(s.runes.elementAt(i)));
}
return buffer.toString();
}
///
/// Add a [char] at a [position] with the given String [s].
///
/// The boolean [repeat] defines whether to add the [char] at every [position].
/// If [position] is greater than the length of [s], it will return [s].
/// If [repeat] is true and [position] is 0, it will return [s].
///
/// Example :
/// 1234567890 , '-', 3 => 123-4567890
/// 1234567890 , '-', 3, true => 123-456-789-0
///
static String addCharAtPosition(String s, String char, int position,
{bool repeat = false}) {
if (!repeat) {
if (s.length < position) {
return s;
}
var before = s.substring(0, position);
var after = s.substring(position, s.length);
return before + char + after;
} else {
if (position == 0) {
return s;
}
var buffer = StringBuffer();
for (var i = 0; i < s.length; i++) {
if (i != 0 && i % position == 0) {
buffer.write(char);
}
buffer.write(String.fromCharCode(s.runes.elementAt(i)));
}
return buffer.toString();
}
}
///
/// Splits the given String [s] in chunks with the given [chunkSize].
///
static List<String> chunk(String s, int chunkSize) {
var chunked = <String>[];
for (var i = 0; i < s.length; i += chunkSize) {
var end = (i + chunkSize < s.length) ? i + chunkSize : s.length;
chunked.add(s.substring(i, end));
}
return chunked;
}
///
/// Picks only required string[value] starting [from] and ending at [to]
///
/// Example :
/// pickOnly('123456789',from:3,to:7);
/// returns '34567'
///
static String pickOnly(value, {int from = 1, int to = -1}) {
try {
return value.substring(
from == 0 ? 0 : from - 1, to == -1 ? value.length : to);
} catch (e) {
return value;
}
}
///
/// Removes character with [index] from a String [value]
///
/// Example:
/// removeCharAtPosition('flutterr', 8);
/// returns 'flutter'
static String removeCharAtPosition(String value, int index) {
try {
return value.substring(0, -1 + index) +
value.substring(index, value.length);
} catch (e) {
return value;
}
}
///
///Remove String[value] with [pattern]
///
///[repeat]:boolean => if(true) removes all occurence
///
///[casensitive]:boolean => if(true) a != A
///
///Example: removeExp('Hello This World', 'This'); returns 'Hello World'
///
static String removeExp(String value, String pattern,
{bool repeat = true,
bool caseSensitive = true,
bool multiLine = false,
bool dotAll = false,
bool unicode = false}) {
var result = value;
if (repeat) {
result = value
.replaceAll(
RegExp(pattern,
caseSensitive: caseSensitive,
multiLine: multiLine,
dotAll: dotAll,
unicode: unicode),
'')
.replaceAll(RegExp(' +'), ' ')
.trim();
} else {
result = value
.replaceFirst(
RegExp(pattern,
caseSensitive: caseSensitive,
multiLine: multiLine,
dotAll: dotAll,
unicode: unicode),
'')
.replaceAll(RegExp(' +'), ' ')
.trim();
}
return result;
}
///
/// Takes in a String[value] and truncates it with [length]
/// [symbol] default is '...'
///truncate('This is a Dart Utility Library', 26)
/// returns 'This is a Dart Utility Lib...'
static String truncate(String value, int length, {String symbol = '...'}) {
var result = value;
try {
result = value.substring(0, length) + symbol;
} catch (e) {
print(e.toString());
}
return result;
}
///Generates a Random string
///
///[length]: length of string,
///
///[alphabet]:(boolean) add alphabet to string[uppercase]ABCD and [lowercase]abcd,
///
///[numeric]:(boolean) add integers to string like 3622737
///
///[special]:(boolean) add special characters like $#@&^
///
///[from]:where you want to generate string from
///
static String generateRandomString(int length,
{alphabet = true,
numeric = true,
special = true,
uppercase = true,
lowercase = true,
String from = ''}) {
var res = '';
do {
res += randomizer(alphabet, numeric, lowercase, uppercase, special, from);
} while (res.length < length);
var possible = res.split('');
possible.shuffle(); //all possible combinations shuffled
var result = [];
for (var i = 0; i < length; i++) {
var randomNumber = Random().nextInt(length);
result.add(possible[randomNumber]);
}
return result.join();
}
static String randomizer(bool alphabet, bool numeric, bool lowercase,
bool uppercase, bool special, String from) {
var a = 'ABCDEFGHIJKLMNOPQRXYZ';
var la = 'abcdefghijklmnopqrxyz';
var b = '0123456789';
var c = '~^!@#\$%^&*;`(=?]:[.)_+-|\{}';
var result = '';
if (alphabet) {
if (lowercase) {
result += la;
}
if (uppercase) {
result += a;
}
if (!uppercase && !lowercase) {
result += a;
result += la;
}
}
if (numeric) {
result += b;
}
if (special) {
result += c;
}
if (from != '') {
//if set return it
result = from;
}
return result;
}
}

+ 35
- 0
mobile/lib/utils/storage/database.dart View File

@ -0,0 +1,35 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
Future<Database> getDatabaseConnection() async {
WidgetsFlutterBinding.ensureInitialized();
final database = openDatabase(
// Set the path to the database. Note: Using the `join` function from the
// `path` package is best practice to ensure the path is correctly
// constructed for each platform.
join(await getDatabasesPath(), 'envelope.db'),
// When the database is first created, create a table to store dogs.
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database.
return db.execute(
'''
CREATE TABLE IF NOT EXISTS friends(
id BLOB PRIMARY KEY,
user_id BLOB,
friend_id BLOB,
friend_id_decrypted BLOB,
accepted_at TEXT
);
''',
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
return database;
}

+ 3
- 3
mobile/lib/utils/storage/encryption_keys.dart View File

@ -1,11 +1,11 @@
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import "package:pointycastle/export.dart"; import "package:pointycastle/export.dart";
import '/utils/encryption/rsa_key_helper.dart';
import '/utils/encryption/crypto_utils.dart';
const rsaPrivateKeyName = 'rsaPrivateKey'; const rsaPrivateKeyName = 'rsaPrivateKey';
void setPrivateKey(RSAPrivateKey key) async { void setPrivateKey(RSAPrivateKey key) async {
String keyPem = RsaKeyHelper.encodePrivateKeyToPem(key);
String keyPem = CryptoUtils.encodeRSAPrivateKeyToPem(key);
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
prefs.setString(rsaPrivateKeyName, keyPem); prefs.setString(rsaPrivateKeyName, keyPem);
@ -23,5 +23,5 @@ Future<RSAPrivateKey> getPrivateKey() async {
throw Exception('No RSA private key set'); throw Exception('No RSA private key set');
} }
return RsaKeyHelper.parsePrivateKeyFromPem(keyPem);
return CryptoUtils.rsaPrivateKeyFromPem(keyPem);
} }

+ 38
- 0
mobile/lib/utils/storage/friends.dart View File

@ -0,0 +1,38 @@
// import 'package:sqflite/sqflite.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import "package:pointycastle/export.dart";
import '/models/friends.dart';
import '/utils/storage/encryption_keys.dart';
import '/utils/storage/session_cookie.dart';
void getFriends() async {
RSAPrivateKey privKey = await getPrivateKey();
final resp = await http.get(
Uri.parse('http://192.168.1.5:8080/api/v1/auth/friends'),
headers: {
'cookie': await getSessionCookie(),
}
);
if (resp.statusCode != 200) {
throw Exception(resp.body);
}
List<Friend> friends = [];
List<dynamic> friendsJson = jsonDecode(resp.body);
for (var i = 0; i < friendsJson.length; i++) {
friends.add(
Friend.fromJson(
friendsJson[i] as Map<String, dynamic>,
privKey,
)
);
}
print(friends);
}

+ 23
- 0
mobile/lib/utils/storage/session_cookie.dart View File

@ -0,0 +1,23 @@
import 'package:shared_preferences/shared_preferences.dart';
const sessionCookieName = 'sessionCookie';
void setSessionCookie(String cookie) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString(sessionCookieName, cookie);
}
void unsetSessionCookie() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(sessionCookieName);
}
Future<String> getSessionCookie() async {
final prefs = await SharedPreferences.getInstance();
String? sessionCookie = prefs.getString(sessionCookieName);
if (sessionCookie == null) {
throw Exception('No session cookie set');
}
return sessionCookie;
}

+ 21
- 13
mobile/lib/views/authentication/login.dart View File

@ -2,9 +2,10 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '/utils/encryption/rsa_key_helper.dart';
import '/utils/encryption/crypto_utils.dart';
import '/utils/encryption/aes_helper.dart'; import '/utils/encryption/aes_helper.dart';
import '/utils/storage/encryption_keys.dart'; import '/utils/storage/encryption_keys.dart';
import '/utils/storage/session_cookie.dart';
class LoginResponse { class LoginResponse {
final String status; final String status;
@ -40,15 +41,24 @@ Future<LoginResponse> login(context, String username, String password) async {
'password': password, 'password': password,
}), }),
); );
LoginResponse response = LoginResponse.fromJson(jsonDecode(resp.body));
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
throw Exception(response.message);
throw Exception(resp.body);
}
String? rawCookie = resp.headers['set-cookie'];
if (rawCookie != null) {
int index = rawCookie.indexOf(';');
setSessionCookie((index == -1) ? rawCookie : rawCookie.substring(0, index));
} }
LoginResponse response = LoginResponse.fromJson(jsonDecode(resp.body));
var rsaPrivPem = AesHelper.aesDecrypt(password, base64.decode(response.asymmetricPrivateKey)); var rsaPrivPem = AesHelper.aesDecrypt(password, base64.decode(response.asymmetricPrivateKey));
var rsaPriv = RsaKeyHelper.parsePrivateKeyFromPem(rsaPrivPem);
debugPrint(rsaPrivPem);
var rsaPriv = CryptoUtils.rsaPrivateKeyFromPem(rsaPrivPem);
setPrivateKey(rsaPriv); setPrivateKey(rsaPriv);
final preferences = await SharedPreferences.getInstance(); final preferences = await SharedPreferences.getInstance();
@ -171,14 +181,12 @@ class _LoginWidgetState extends State<LoginWidget> {
usernameController.text, usernameController.text,
passwordController.text, passwordController.text,
).then((value) { ).then((value) {
/*
Navigator.of(context).popUntil((route) {
print(route.isFirst);
return route.isFirst;
});
*/
Navigator.pushNamedAndRemoveUntil(context, '/home', ModalRoute.withName('/home'));
Navigator.
pushNamedAndRemoveUntil(
context,
'/home',
ModalRoute.withName('/home'),
);
}).catchError((error) { }).catchError((error) {
print(error); // TODO: Show error on interface print(error); // TODO: Show error on interface
}); });


+ 9
- 12
mobile/lib/views/authentication/signup.dart View File

@ -4,11 +4,9 @@ import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '/views/main/conversation_list.dart';
import '/utils/encryption/rsa_key_helper.dart';
import '/utils/encryption/aes_helper.dart'; import '/utils/encryption/aes_helper.dart';
import '/utils/storage/encryption_keys.dart'; import '/utils/storage/encryption_keys.dart';
import '/utils/encryption/crypto_utils.dart';
class SignupResponse { class SignupResponse {
final String status; final String status;
@ -28,16 +26,14 @@ class SignupResponse {
} }
Future<SignupResponse> signUp(context, String username, String password, String confirmPassword) async { Future<SignupResponse> signUp(context, String username, String password, String confirmPassword) async {
var rsaKeyHelper = RsaKeyHelper();
var keyPair = rsaKeyHelper.generateRSAkeyPair();
setPrivateKey(keyPair.privateKey);
var keyPair = CryptoUtils.generateRSAKeyPair();
var rsaPubPem = RsaKeyHelper.encodePublicKeyToPem(keyPair.publicKey);
var rsaPrivPem = RsaKeyHelper.encodePrivateKeyToPem(keyPair.privateKey);
var rsaPubPem = CryptoUtils.encodeRSAPublicKeyToPem(keyPair.publicKey);
var rsaPrivPem = CryptoUtils.encodeRSAPrivateKeyToPem(keyPair.privateKey);
var encRsaPriv = AesHelper.aesEncrypt(password, Uint8List.fromList(rsaPrivPem.codeUnits)); var encRsaPriv = AesHelper.aesEncrypt(password, Uint8List.fromList(rsaPrivPem.codeUnits));
// TODO: Check for timeout here
final resp = await http.post( final resp = await http.post(
Uri.parse('http://192.168.1.5:8080/api/v1/signup'), Uri.parse('http://192.168.1.5:8080/api/v1/signup'),
headers: <String, String>{ headers: <String, String>{
@ -52,14 +48,15 @@ Future<SignupResponse> signUp(context, String username, String password, String
}), }),
); );
SignupResponse response = SignupResponse.fromJson(jsonDecode(resp.body));
SignupResponse response = SignupResponse.fromJson(jsonDecode(resp.body));
if (resp.statusCode != 201) { if (resp.statusCode != 201) {
throw Exception(response.message); throw Exception(response.message);
} }
final preferences = await SharedPreferences.getInstance();
preferences.setBool('islogin', true);
debugPrint(rsaPubPem);
debugPrint(rsaPrivPem);
debugPrint(resp.body);
return response; return response;
} }


+ 10
- 8
mobile/lib/views/main/friend_list.dart View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '/models/friends.dart'; import '/models/friends.dart';
import '/views/main/friend_list_item.dart'; import '/views/main/friend_list_item.dart';
import '/utils/storage/friends.dart';
class FriendList extends StatefulWidget { class FriendList extends StatefulWidget {
const FriendList({Key? key}) : super(key: key); const FriendList({Key? key}) : super(key: key);
@ -10,13 +11,14 @@ class FriendList extends StatefulWidget {
} }
class _FriendListState extends State<FriendList> { class _FriendListState extends State<FriendList> {
List<Friend> friends = [
Friend(id: 'abc', username: 'Test1'),
Friend(id: 'abc', username: 'Test2'),
Friend(id: 'abc', username: 'Test3'),
Friend(id: 'abc', username: 'Test4'),
Friend(id: 'abc', username: 'Test5'),
];
List<Friend> friends = [];
@override
void initState() {
getFriends();
super.initState();
}
Widget list() { Widget list() {
@ -34,7 +36,7 @@ class _FriendListState extends State<FriendList> {
itemBuilder: (context, i) { itemBuilder: (context, i) {
return FriendListItem( return FriendListItem(
id: friends[i].id, id: friends[i].id,
username: friends[i].username,
username: 'test',
); );
}, },
); );


+ 22
- 1
mobile/pubspec.lock View File

@ -164,7 +164,7 @@ packages:
source: hosted source: hosted
version: "1.7.0" version: "1.7.0"
path: path:
dependency: transitive
dependency: "direct main"
description: description:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
@ -287,6 +287,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.2" version: "1.8.2"
sqflite:
dependency: "direct main"
description:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2+1"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1+1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -308,6 +322,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
synchronized:
dependency: transitive
description:
name: synchronized
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0+2"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:


+ 2
- 1
mobile/pubspec.yaml View File

@ -11,13 +11,14 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
font_awesome_flutter: ^10.1.0 font_awesome_flutter: ^10.1.0
pointycastle: ^3.5.2 pointycastle: ^3.5.2
asn1lib: ^1.1.0 asn1lib: ^1.1.0
http: ^0.13.4 http: ^0.13.4
shared_preferences: ^2.0.15 shared_preferences: ^2.0.15
sqflite: ^2.0.2
path: 1.8.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:


Loading…
Cancel
Save