@ -0,0 +1,107 @@ | |||||
package Auth | |||||
import ( | |||||
"encoding/json" | |||||
"net/http" | |||||
"time" | |||||
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" | |||||
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" | |||||
"github.com/gofrs/uuid" | |||||
) | |||||
type Credentials struct { | |||||
Username string `json:"username"` | |||||
Password string `json:"password"` | |||||
} | |||||
type loginResponse struct { | |||||
Status string `json:"status"` | |||||
Message string `json:"message"` | |||||
AsymmetricPublicKey string `json:"asymmetric_public_key"` | |||||
AsymmetricPrivateKey string `json:"asymmetric_private_key"` | |||||
} | |||||
func makeLoginResponse(w http.ResponseWriter, code int, message, pubKey, privKey string) { | |||||
var ( | |||||
status string = "error" | |||||
returnJson []byte | |||||
err error | |||||
) | |||||
if code > 200 && code < 300 { | |||||
status = "success" | |||||
} | |||||
returnJson, err = json.MarshalIndent(loginResponse{ | |||||
Status: status, | |||||
Message: message, | |||||
AsymmetricPublicKey: pubKey, | |||||
AsymmetricPrivateKey: privKey, | |||||
}, "", " ") | |||||
if err != nil { | |||||
http.Error(w, "Error", http.StatusInternalServerError) | |||||
w.WriteHeader(http.StatusInternalServerError) | |||||
return | |||||
} | |||||
// Return updated json | |||||
w.WriteHeader(code) | |||||
w.Write(returnJson) | |||||
} | |||||
func Login(w http.ResponseWriter, r *http.Request) { | |||||
var ( | |||||
creds Credentials | |||||
userData Models.User | |||||
sessionToken uuid.UUID | |||||
expiresAt time.Time | |||||
err error | |||||
) | |||||
err = json.NewDecoder(r.Body).Decode(&creds) | |||||
if err != nil { | |||||
makeLoginResponse(w, http.StatusInternalServerError, "An error occurred", "", "") | |||||
return | |||||
} | |||||
userData, err = Database.GetUserByUsername(creds.Username) | |||||
if err != nil { | |||||
makeLoginResponse(w, http.StatusUnauthorized, "An error occurred", "", "") | |||||
return | |||||
} | |||||
if !CheckPasswordHash(creds.Password, userData.Password) { | |||||
makeLoginResponse(w, http.StatusUnauthorized, "An error occurred", "", "") | |||||
return | |||||
} | |||||
sessionToken, err = uuid.NewV4() | |||||
if err != nil { | |||||
makeLoginResponse(w, http.StatusInternalServerError, "An error occurred", "", "") | |||||
return | |||||
} | |||||
expiresAt = time.Now().Add(1 * time.Hour) | |||||
Sessions[sessionToken.String()] = Session{ | |||||
UserID: userData.ID.String(), | |||||
Username: userData.Username, | |||||
Expiry: expiresAt, | |||||
} | |||||
http.SetCookie(w, &http.Cookie{ | |||||
Name: "session_token", | |||||
Value: sessionToken.String(), | |||||
Expires: expiresAt, | |||||
}) | |||||
makeLoginResponse( | |||||
w, | |||||
http.StatusOK, | |||||
"Successfully logged in", | |||||
userData.AsymmetricPublicKey, | |||||
userData.AsymmetricPrivateKey, | |||||
) | |||||
} |
@ -0,0 +1,79 @@ | |||||
package Auth | |||||
import ( | |||||
"errors" | |||||
"net/http" | |||||
"time" | |||||
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" | |||||
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" | |||||
) | |||||
var ( | |||||
Sessions = map[string]Session{} | |||||
) | |||||
type Session struct { | |||||
UserID string | |||||
Username string | |||||
Expiry time.Time | |||||
} | |||||
func (s Session) IsExpired() bool { | |||||
return s.Expiry.Before(time.Now()) | |||||
} | |||||
func CheckCookie(r *http.Request) (Session, error) { | |||||
var ( | |||||
c *http.Cookie | |||||
sessionToken string | |||||
userSession Session | |||||
exists bool | |||||
err error | |||||
) | |||||
c, err = r.Cookie("session_token") | |||||
if err != nil { | |||||
return userSession, err | |||||
} | |||||
sessionToken = c.Value | |||||
// We then get the session from our session map | |||||
userSession, exists = Sessions[sessionToken] | |||||
if !exists { | |||||
return userSession, errors.New("Cookie not found") | |||||
} | |||||
// If the session is present, but has expired, we can delete the session, and return | |||||
// an unauthorized status | |||||
if userSession.IsExpired() { | |||||
delete(Sessions, sessionToken) | |||||
return userSession, errors.New("Cookie expired") | |||||
} | |||||
return userSession, nil | |||||
} | |||||
func CheckCookieCurrentUser(w http.ResponseWriter, r *http.Request) (Models.User, error) { | |||||
var ( | |||||
userSession Session | |||||
userData Models.User | |||||
err error | |||||
) | |||||
userSession, err = CheckCookie(r) | |||||
if err != nil { | |||||
return userData, err | |||||
} | |||||
userData, err = Database.GetUserById(userSession.UserID) | |||||
if err != nil { | |||||
return userData, err | |||||
} | |||||
if userData.ID.String() != userSession.UserID { | |||||
return userData, errors.New("Is not current user") | |||||
} | |||||
return userData, nil | |||||
} |
@ -1,75 +0,0 @@ | |||||
import 'package:flutter/material.dart'; | |||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | |||||
import './signup.dart'; | |||||
class UnauthenticatedLandingWidget extends StatefulWidget { | |||||
const UnauthenticatedLandingWidget({Key? key}) : super(key: key); | |||||
@override | |||||
State<UnauthenticatedLandingWidget> createState() => _UnauthenticatedLandingWidgetState(); | |||||
} | |||||
class _UnauthenticatedLandingWidgetState extends State<UnauthenticatedLandingWidget> { | |||||
@override | |||||
Widget build(BuildContext context) { | |||||
final ButtonStyle buttonStyle = ElevatedButton.styleFrom( | |||||
primary: Colors.white, | |||||
onPrimary: Colors.cyan, | |||||
minimumSize: const Size.fromHeight(50), | |||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), | |||||
textStyle: const TextStyle( | |||||
fontSize: 20, | |||||
fontWeight: FontWeight.bold, | |||||
color: Colors.red, | |||||
), | |||||
); | |||||
return Center( | |||||
child: Column( | |||||
mainAxisAlignment: MainAxisAlignment.center, | |||||
crossAxisAlignment: CrossAxisAlignment.center, | |||||
children: <Widget>[ | |||||
Center( | |||||
child: Row( | |||||
mainAxisAlignment: MainAxisAlignment.center, | |||||
crossAxisAlignment: CrossAxisAlignment.center, | |||||
children: const [ | |||||
FaIcon(FontAwesomeIcons.envelope, color: Colors.white, size: 40), | |||||
SizedBox(width: 15), | |||||
Text('Envelope', style: TextStyle(fontSize: 40, color: Colors.white),) | |||||
] | |||||
), | |||||
), | |||||
const SizedBox(height: 10), | |||||
Padding( | |||||
padding: const EdgeInsets.all(15), | |||||
child: Column ( | |||||
children: [ | |||||
ElevatedButton( | |||||
child: const Text('Login'), | |||||
onPressed: loginButton, | |||||
style: buttonStyle, | |||||
), | |||||
const SizedBox(height: 20), | |||||
ElevatedButton( | |||||
child: const Text('Sign Up'), | |||||
onPressed: () => { | |||||
Navigator.push( | |||||
context, | |||||
MaterialPageRoute(builder: (context) => const Signup()), | |||||
), | |||||
}, | |||||
style: buttonStyle, | |||||
), | |||||
] | |||||
), | |||||
), | |||||
], | |||||
), | |||||
); | |||||
} | |||||
} | |||||
void loginButton() { | |||||
} |
@ -0,0 +1,138 @@ | |||||
import 'dart:convert'; | |||||
import 'dart:typed_data'; | |||||
import "package:pointycastle/export.dart"; | |||||
Uint8List createUint8ListFromString(String s) { | |||||
var ret = Uint8List(s.length); | |||||
for (var i = 0; i < s.length; i++) { | |||||
ret[i] = s.codeUnitAt(i); | |||||
} | |||||
return ret; | |||||
} | |||||
// AES key size | |||||
const keySize = 32; // 32 byte key for AES-256 | |||||
const iterationCount = 1000; | |||||
class AesHelper { | |||||
static const cbcMode = 'CBC'; | |||||
static const cfbMode = 'CFB'; | |||||
static Uint8List deriveKey(dynamic password, | |||||
{String salt = '', | |||||
int iterationCount = iterationCount, | |||||
int derivedKeyLength = keySize}) { | |||||
if (password == null || password.isEmpty) { | |||||
throw ArgumentError('password must not be empty'); | |||||
} | |||||
if (password is String) { | |||||
password = createUint8ListFromString(password); | |||||
} | |||||
Uint8List saltBytes = createUint8ListFromString(salt); | |||||
Pbkdf2Parameters params = Pbkdf2Parameters(saltBytes, iterationCount, derivedKeyLength); | |||||
KeyDerivator keyDerivator = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64)); | |||||
keyDerivator.init(params); | |||||
return keyDerivator.process(password); | |||||
} | |||||
static Uint8List pad(Uint8List src, int blockSize) { | |||||
var pad = PKCS7Padding(); | |||||
pad.init(null); | |||||
int padLength = blockSize - (src.length % blockSize); | |||||
var out = Uint8List(src.length + padLength)..setAll(0, src); | |||||
pad.addPadding(out, src.length); | |||||
return out; | |||||
} | |||||
static Uint8List unpad(Uint8List src) { | |||||
var pad = PKCS7Padding(); | |||||
pad.init(null); | |||||
int padLength = pad.padCount(src); | |||||
int len = src.length - padLength; | |||||
return Uint8List(len)..setRange(0, len, src); | |||||
} | |||||
static String aesEncrypt(String password, Uint8List plaintext, | |||||
{String mode = cbcMode}) { | |||||
Uint8List derivedKey = deriveKey(password); | |||||
KeyParameter keyParam = KeyParameter(derivedKey); | |||||
BlockCipher aes = AESEngine(); | |||||
var rnd = FortunaRandom(); | |||||
rnd.seed(keyParam); | |||||
Uint8List iv = rnd.nextBytes(aes.blockSize); | |||||
BlockCipher cipher; | |||||
ParametersWithIV params = ParametersWithIV(keyParam, iv); | |||||
switch (mode) { | |||||
case cbcMode: | |||||
cipher = CBCBlockCipher(aes); | |||||
break; | |||||
case cfbMode: | |||||
cipher = CFBBlockCipher(aes, aes.blockSize); | |||||
break; | |||||
default: | |||||
throw ArgumentError('incorrect value of the "mode" parameter'); | |||||
} | |||||
cipher.init(true, params); | |||||
Uint8List paddedText = pad(plaintext, aes.blockSize); | |||||
Uint8List cipherBytes = _processBlocks(cipher, paddedText); | |||||
Uint8List cipherIvBytes = Uint8List(cipherBytes.length + iv.length) | |||||
..setAll(0, iv) | |||||
..setAll(iv.length, cipherBytes); | |||||
return base64.encode(cipherIvBytes); | |||||
} | |||||
static String aesDecrypt(String password, Uint8List ciphertext, | |||||
{String mode = cbcMode}) { | |||||
Uint8List derivedKey = deriveKey(password); | |||||
KeyParameter keyParam = KeyParameter(derivedKey); | |||||
BlockCipher aes = AESEngine(); | |||||
Uint8List iv = Uint8List(aes.blockSize) | |||||
..setRange(0, aes.blockSize, ciphertext); | |||||
BlockCipher cipher; | |||||
ParametersWithIV params = ParametersWithIV(keyParam, iv); | |||||
switch (mode) { | |||||
case cbcMode: | |||||
cipher = CBCBlockCipher(aes); | |||||
break; | |||||
case cfbMode: | |||||
cipher = CFBBlockCipher(aes, aes.blockSize); | |||||
break; | |||||
default: | |||||
throw ArgumentError('incorrect value of the "mode" parameter'); | |||||
} | |||||
cipher.init(false, params); | |||||
int cipherLen = ciphertext.length - aes.blockSize; | |||||
Uint8List cipherBytes = Uint8List(cipherLen) | |||||
..setRange(0, cipherLen, ciphertext, aes.blockSize); | |||||
Uint8List paddedText = _processBlocks(cipher, cipherBytes); | |||||
Uint8List textBytes = unpad(paddedText); | |||||
return String.fromCharCodes(textBytes); | |||||
} | |||||
static Uint8List _processBlocks(BlockCipher cipher, Uint8List inp) { | |||||
var out = Uint8List(inp.lengthInBytes); | |||||
for (var offset = 0; offset < inp.lengthInBytes;) { | |||||
var len = cipher.processBlock(inp, offset, out, offset); | |||||
offset += len; | |||||
} | |||||
return out; | |||||
} | |||||
} |
@ -0,0 +1,257 @@ | |||||
import 'dart:convert'; | |||||
import 'dart:math'; | |||||
import 'dart:typed_data'; | |||||
import 'package:pointycastle/src/platform_check/platform_check.dart'; | |||||
import "package:pointycastle/export.dart"; | |||||
import "package:asn1lib/asn1lib.dart"; | |||||
/* | |||||
var rsaKeyHelper = RsaKeyHelper(); | |||||
var keyPair = rsaKeyHelper.generateRSAkeyPair(); | |||||
var rsaPub = keyPair.publicKey; | |||||
var rsaPriv = keyPair.privateKey; | |||||
var cipherText = rsaKeyHelper.rsaEncrypt(rsaPub, Uint8List.fromList('Test Data'.codeUnits)); | |||||
print(cipherText); | |||||
var plainText = rsaKeyHelper.rsaDecrypt(rsaPriv, cipherText); | |||||
print(String.fromCharCodes(plainText)); | |||||
*/ | |||||
List<int> decodePEM(String pem) { | |||||
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", | |||||
]; | |||||
var endsWith = [ | |||||
"-----END PUBLIC KEY-----", | |||||
"-----END PRIVATE KEY-----", | |||||
"-----END PGP PUBLIC KEY BLOCK-----", | |||||
"-----END PGP PRIVATE KEY BLOCK-----", | |||||
]; | |||||
bool isOpenPgp = pem.contains('BEGIN PGP'); | |||||
for (var s in startsWith) { | |||||
if (pem.startsWith(s)) { | |||||
pem = pem.substring(s.length); | |||||
} | |||||
} | |||||
for (var s in endsWith) { | |||||
if (pem.endsWith(s)) { | |||||
pem = pem.substring(0, pem.length - s.length); | |||||
} | |||||
} | |||||
if (isOpenPgp) { | |||||
var index = pem.indexOf('\r\n'); | |||||
pem = pem.substring(0, index); | |||||
} | |||||
pem = pem.replaceAll('\n', ''); | |||||
pem = pem.replaceAll('\r', ''); | |||||
return base64.decode(pem); | |||||
} | |||||
class RsaKeyHelper { | |||||
// Generate secure random sequence for seed | |||||
SecureRandom secureRandom() { | |||||
var secureRandom = FortunaRandom(); | |||||
var random = Random.secure(); | |||||
List<int> seeds = []; | |||||
for (int i = 0; i < 32; i++) { | |||||
seeds.add(random.nextInt(255)); | |||||
} | |||||
secureRandom.seed(KeyParameter(Uint8List.fromList(seeds))); | |||||
return secureRandom; | |||||
} | |||||
// Generate RSA key pair | |||||
AsymmetricKeyPair<RSAPublicKey, RSAPrivateKey> generateRSAkeyPair({int bitLength = 2048}) { | |||||
var secureRandom = this.secureRandom(); | |||||
// final keyGen = KeyGenerator('RSA'); // Get using registry | |||||
final keyGen = RSAKeyGenerator(); | |||||
keyGen.init(ParametersWithRandom( | |||||
RSAKeyGeneratorParameters(BigInt.parse('65537'), bitLength, 64), | |||||
secureRandom)); | |||||
// Use the generator | |||||
final pair = keyGen.generateKeyPair(); | |||||
// Cast the generated key pair into the RSA key types | |||||
final myPublic = pair.publicKey as RSAPublicKey; | |||||
final myPrivate = pair.privateKey as RSAPrivateKey; | |||||
return AsymmetricKeyPair<RSAPublicKey, RSAPrivateKey>(myPublic, myPrivate); | |||||
} | |||||
// Encrypt data using RSA key | |||||
Uint8List rsaEncrypt(RSAPublicKey myPublic, Uint8List dataToEncrypt) { | |||||
final encryptor = OAEPEncoding(RSAEngine()) | |||||
..init(true, PublicKeyParameter<RSAPublicKey>(myPublic)); // true=encrypt | |||||
return _processInBlocks(encryptor, dataToEncrypt); | |||||
} | |||||
// Decrypt data using RSA key | |||||
Uint8List rsaDecrypt(RSAPrivateKey myPrivate, Uint8List cipherText) { | |||||
final decryptor = OAEPEncoding(RSAEngine()) | |||||
..init(false, PrivateKeyParameter<RSAPrivateKey>(myPrivate)); // false=decrypt | |||||
return _processInBlocks(decryptor, cipherText); | |||||
} | |||||
// Process blocks after encryption/descryption | |||||
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); | |||||
} | |||||
// Encode RSA public key to pem format | |||||
static encodePublicKeyToPem(RSAPublicKey publicKey) { | |||||
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 publicKeySeq = ASN1Sequence(); | |||||
publicKeySeq.add(ASN1Integer(publicKey.modulus!)); | |||||
publicKeySeq.add(ASN1Integer(publicKey.exponent!)); | |||||
var publicKeySeqBitString = ASN1BitString(Uint8List.fromList(publicKeySeq.encodedBytes)); | |||||
var topLevelSeq = ASN1Sequence(); | |||||
topLevelSeq.add(algorithmSeq); | |||||
topLevelSeq.add(publicKeySeqBitString); | |||||
var dataBase64 = base64.encode(topLevelSeq.encodedBytes); | |||||
return """-----BEGIN PUBLIC KEY-----\r\n$dataBase64\r\n-----END PUBLIC KEY-----"""; | |||||
} | |||||
// Parse public key PEM file | |||||
static parsePublicKeyFromPem(pemString) { | |||||
List<int> publicKeyDER = decodePEM(pemString); | |||||
var asn1Parser = ASN1Parser(Uint8List.fromList(publicKeyDER)); | |||||
var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; | |||||
var publicKeyBitString = topLevelSeq.elements[1]; | |||||
var publicKeyAsn = ASN1Parser(publicKeyBitString.contentBytes()!, relaxedParsing: true); | |||||
var publicKeySeq = publicKeyAsn.nextObject() as ASN1Sequence; | |||||
var modulus = publicKeySeq.elements[0] as ASN1Integer; | |||||
var exponent = publicKeySeq.elements[1] as ASN1Integer; | |||||
RSAPublicKey rsaPublicKey = RSAPublicKey( | |||||
modulus.valueAsBigInteger!, | |||||
exponent.valueAsBigInteger! | |||||
); | |||||
return rsaPublicKey; | |||||
} | |||||
// Encode RSA private key to pem format | |||||
static encodePrivateKeyToPem(RSAPrivateKey privateKey) { | |||||
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(privateKey.n!); | |||||
var publicExponent = ASN1Integer(BigInt.parse('65537')); | |||||
var privateExponent = ASN1Integer(privateKey.privateExponent!); | |||||
var p = ASN1Integer(privateKey.p!); | |||||
var q = ASN1Integer(privateKey.q!); | |||||
var dP = privateKey.privateExponent! % (privateKey.p! - BigInt.from(1)); | |||||
var exp1 = ASN1Integer(dP); | |||||
var dQ = privateKey.privateExponent! % (privateKey.q! - BigInt.from(1)); | |||||
var exp2 = ASN1Integer(dQ); | |||||
var iQ = privateKey.q?.modInverse(privateKey.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(Uint8List.fromList(privateKeySeq.encodedBytes)); | |||||
var topLevelSeq = ASN1Sequence(); | |||||
topLevelSeq.add(version); | |||||
topLevelSeq.add(algorithmSeq); | |||||
topLevelSeq.add(publicKeySeqOctetString); | |||||
var dataBase64 = base64.encode(topLevelSeq.encodedBytes); | |||||
return """-----BEGIN PRIVATE KEY-----\r\n$dataBase64\r\n-----END PRIVATE KEY-----"""; | |||||
} | |||||
static parsePrivateKeyFromPem(pemString) { | |||||
List<int> privateKeyDER = decodePEM(pemString); | |||||
var asn1Parser = ASN1Parser(Uint8List.fromList(privateKeyDER)); | |||||
var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; | |||||
var version = topLevelSeq.elements[0]; | |||||
var algorithm = topLevelSeq.elements[1]; | |||||
var privateKey = topLevelSeq.elements[2]; | |||||
asn1Parser = ASN1Parser(privateKey.contentBytes()!); | |||||
var pkSeq = asn1Parser.nextObject() as ASN1Sequence; | |||||
version = pkSeq.elements[0]; | |||||
var modulus = pkSeq.elements[1] as ASN1Integer; | |||||
//var 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; | |||||
var exp1 = pkSeq.elements[6] as ASN1Integer; | |||||
var exp2 = pkSeq.elements[7] as ASN1Integer; | |||||
var co = pkSeq.elements[8] as ASN1Integer; | |||||
RSAPrivateKey rsaPrivateKey = RSAPrivateKey( | |||||
modulus.valueAsBigInteger!, | |||||
privateExponent.valueAsBigInteger!, | |||||
p.valueAsBigInteger, | |||||
q.valueAsBigInteger | |||||
); | |||||
return rsaPrivateKey; | |||||
} | |||||
} |
@ -0,0 +1,22 @@ | |||||
import 'package:shared_preferences/shared_preferences.dart'; | |||||
import "package:pointycastle/export.dart"; | |||||
import '/utils/encryption/rsa_key_helper.dart'; | |||||
const rsaPrivateKeyName = 'rsaPrivateKey'; | |||||
void setPrivateKey(RSAPrivateKey key) async { | |||||
String keyPem = RsaKeyHelper.encodePrivateKeyToPem(key); | |||||
final prefs = await SharedPreferences.getInstance(); | |||||
prefs.setString(rsaPrivateKeyName, keyPem); | |||||
} | |||||
Future<RSAPrivateKey> getPrivateKey() async { | |||||
final prefs = await SharedPreferences.getInstance(); | |||||
String? keyPem = prefs.getString(rsaPrivateKeyName); | |||||
if (keyPem == null) { | |||||
throw Exception('No RSA private key set'); | |||||
} | |||||
return RsaKeyHelper.parsePrivateKeyFromPem(keyPem); | |||||
} |
@ -0,0 +1,217 @@ | |||||
import 'dart:convert'; | |||||
import 'package:flutter/material.dart'; | |||||
import 'package:http/http.dart' as http; | |||||
import 'package:shared_preferences/shared_preferences.dart'; | |||||
import '/views/main/conversations_list.dart'; | |||||
import '/utils/encryption/rsa_key_helper.dart'; | |||||
import '/utils/encryption/aes_helper.dart'; | |||||
import '/utils/storage/encryption_keys.dart'; | |||||
class LoginResponse { | |||||
final String status; | |||||
final String message; | |||||
final String asymmetricPublicKey; | |||||
final String asymmetricPrivateKey; | |||||
const LoginResponse({ | |||||
required this.status, | |||||
required this.message, | |||||
required this.asymmetricPublicKey, | |||||
required this.asymmetricPrivateKey, | |||||
}); | |||||
factory LoginResponse.fromJson(Map<String, dynamic> json) { | |||||
return LoginResponse( | |||||
status: json['status'], | |||||
message: json['message'], | |||||
asymmetricPublicKey: json['asymmetric_public_key'], | |||||
asymmetricPrivateKey: json['asymmetric_private_key'], | |||||
); | |||||
} | |||||
} | |||||
Future<LoginResponse> login(context, String username, String password) async { | |||||
final resp = await http.post( | |||||
Uri.parse('http://192.168.1.5:8080/api/v1/login'), | |||||
headers: <String, String>{ | |||||
'Content-Type': 'application/json; charset=UTF-8', | |||||
}, | |||||
body: jsonEncode(<String, String>{ | |||||
'username': username, | |||||
'password': password, | |||||
}), | |||||
); | |||||
LoginResponse response = LoginResponse.fromJson(jsonDecode(resp.body)); | |||||
if (resp.statusCode != 200) { | |||||
throw Exception(response.message); | |||||
} | |||||
var rsaPrivPem = AesHelper.aesDecrypt(password, base64.decode(response.asymmetricPrivateKey)); | |||||
var rsaPriv = RsaKeyHelper.parsePrivateKeyFromPem(rsaPrivPem); | |||||
setPrivateKey(rsaPriv); | |||||
final preferences = await SharedPreferences.getInstance(); | |||||
preferences.setBool('islogin', true); | |||||
return response; | |||||
} | |||||
class Login extends StatelessWidget { | |||||
const Login({Key? key}) : super(key: key); | |||||
static const String _title = 'Envelope'; | |||||
@override | |||||
Widget build(BuildContext context) { | |||||
return MaterialApp( | |||||
title: _title, | |||||
home: Scaffold( | |||||
backgroundColor: Colors.cyan, | |||||
appBar: AppBar( | |||||
title: null, | |||||
automaticallyImplyLeading: true, | |||||
//`true` if you want Flutter to automatically add Back Button when needed, | |||||
//or `false` if you want to force your own back button every where | |||||
leading: IconButton(icon: const Icon(Icons.arrow_back), | |||||
//onPressed:() => Navigator.pop(context, false), | |||||
onPressed:() => { | |||||
Navigator.pop(context) | |||||
} | |||||
) | |||||
), | |||||
body: const SafeArea( | |||||
child: LoginWidget(), | |||||
) | |||||
), | |||||
theme: ThemeData( | |||||
appBarTheme: const AppBarTheme( | |||||
backgroundColor: Colors.cyan, | |||||
elevation: 0, | |||||
), | |||||
inputDecorationTheme: const InputDecorationTheme( | |||||
border: OutlineInputBorder(), | |||||
focusedBorder: OutlineInputBorder(), | |||||
labelStyle: TextStyle( | |||||
color: Colors.white, | |||||
fontSize: 30, | |||||
), | |||||
filled: true, | |||||
fillColor: Colors.white, | |||||
), | |||||
), | |||||
); | |||||
} | |||||
} | |||||
class LoginWidget extends StatefulWidget { | |||||
const LoginWidget({Key? key}) : super(key: key); | |||||
@override | |||||
State<LoginWidget> createState() => _LoginWidgetState(); | |||||
} | |||||
class _LoginWidgetState extends State<LoginWidget> { | |||||
final _formKey = GlobalKey<FormState>(); | |||||
TextEditingController usernameController = TextEditingController(); | |||||
TextEditingController passwordController = TextEditingController(); | |||||
@override | |||||
Widget build(BuildContext context) { | |||||
const TextStyle _inputTextStyle = TextStyle(fontSize: 18, color: Colors.black); | |||||
final ButtonStyle _buttonStyle = ElevatedButton.styleFrom( | |||||
primary: Colors.white, | |||||
onPrimary: Colors.cyan, | |||||
minimumSize: const Size.fromHeight(50), | |||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), | |||||
textStyle: const TextStyle( | |||||
fontSize: 20, | |||||
fontWeight: FontWeight.bold, | |||||
color: Colors.red, | |||||
), | |||||
); | |||||
return Center( | |||||
child: Form( | |||||
key: _formKey, | |||||
child: Center( | |||||
child: Padding( | |||||
padding: const EdgeInsets.all(15), | |||||
child: Column( | |||||
mainAxisAlignment: MainAxisAlignment.center, | |||||
crossAxisAlignment: CrossAxisAlignment.center, | |||||
children: [ | |||||
const Text('Login', style: TextStyle(fontSize: 35, color: Colors.white),), | |||||
const SizedBox(height: 30), | |||||
TextFormField( | |||||
controller: usernameController, | |||||
decoration: const InputDecoration( | |||||
hintText: 'Username', | |||||
), | |||||
style: _inputTextStyle, | |||||
// The validator receives the text that the user has entered. | |||||
validator: (value) { | |||||
if (value == null || value.isEmpty) { | |||||
return 'Create a username'; | |||||
} | |||||
return null; | |||||
}, | |||||
), | |||||
const SizedBox(height: 5), | |||||
TextFormField( | |||||
controller: passwordController, | |||||
obscureText: true, | |||||
enableSuggestions: false, | |||||
autocorrect: false, | |||||
decoration: const InputDecoration( | |||||
hintText: 'Password', | |||||
), | |||||
style: _inputTextStyle, | |||||
// The validator receives the text that the user has entered. | |||||
validator: (value) { | |||||
if (value == null || value.isEmpty) { | |||||
return 'Enter a password'; | |||||
} | |||||
return null; | |||||
}, | |||||
), | |||||
const SizedBox(height: 5), | |||||
ElevatedButton( | |||||
style: _buttonStyle, | |||||
onPressed: () { | |||||
if (_formKey.currentState!.validate()) { | |||||
ScaffoldMessenger.of(context).showSnackBar( | |||||
const SnackBar(content: Text('Processing Data')), | |||||
); | |||||
login( | |||||
context, | |||||
usernameController.text, | |||||
passwordController.text, | |||||
).then((value) { | |||||
Navigator.of(context).popUntil((route) { | |||||
print(route.isFirst); | |||||
return route.isFirst; | |||||
}); | |||||
}).catchError((error) { | |||||
print(error); // TODO: Show error on interface | |||||
}); | |||||
} | |||||
}, | |||||
child: const Text('Submit'), | |||||
), | |||||
], | |||||
) | |||||
) | |||||
) | |||||
) | |||||
); | |||||
} | |||||
} |
@ -0,0 +1,85 @@ | |||||
import 'package:flutter/material.dart'; | |||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | |||||
import './login.dart'; | |||||
import './signup.dart'; | |||||
class UnauthenticatedLandingWidget extends StatefulWidget { | |||||
const UnauthenticatedLandingWidget({Key? key}) : super(key: key); | |||||
@override | |||||
State<UnauthenticatedLandingWidget> createState() => _UnauthenticatedLandingWidgetState(); | |||||
} | |||||
class _UnauthenticatedLandingWidgetState extends State<UnauthenticatedLandingWidget> { | |||||
@override | |||||
Widget build(BuildContext context) { | |||||
final ButtonStyle buttonStyle = ElevatedButton.styleFrom( | |||||
primary: Colors.white, | |||||
onPrimary: Colors.cyan, | |||||
minimumSize: const Size.fromHeight(50), | |||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), | |||||
textStyle: const TextStyle( | |||||
fontSize: 20, | |||||
fontWeight: FontWeight.bold, | |||||
color: Colors.red, | |||||
), | |||||
); | |||||
return WillPopScope( | |||||
onWillPop: () async => false, | |||||
child: Scaffold( | |||||
backgroundColor: Colors.cyan, | |||||
body: SafeArea( | |||||
child: Center( | |||||
child: Column( | |||||
mainAxisAlignment: MainAxisAlignment.center, | |||||
crossAxisAlignment: CrossAxisAlignment.center, | |||||
children: <Widget>[ | |||||
Center( | |||||
child: Row( | |||||
mainAxisAlignment: MainAxisAlignment.center, | |||||
crossAxisAlignment: CrossAxisAlignment.center, | |||||
children: const [ | |||||
FaIcon(FontAwesomeIcons.envelope, color: Colors.white, size: 40), | |||||
SizedBox(width: 15), | |||||
Text('Envelope', style: TextStyle(fontSize: 40, color: Colors.white),) | |||||
] | |||||
), | |||||
), | |||||
const SizedBox(height: 10), | |||||
Padding( | |||||
padding: const EdgeInsets.all(15), | |||||
child: Column ( | |||||
children: [ | |||||
ElevatedButton( | |||||
child: const Text('Login'), | |||||
onPressed: () => { | |||||
Navigator.of(context).push( | |||||
MaterialPageRoute(builder: (context) => const Login()), | |||||
), | |||||
}, | |||||
style: buttonStyle, | |||||
), | |||||
const SizedBox(height: 20), | |||||
ElevatedButton( | |||||
child: const Text('Sign Up'), | |||||
onPressed: () => { | |||||
Navigator.of(context).push( | |||||
MaterialPageRoute(builder: (context) => const Signup()), | |||||
), | |||||
}, | |||||
style: buttonStyle, | |||||
), | |||||
] | |||||
), | |||||
), | |||||
], | |||||
), | |||||
), | |||||
), | |||||
), | |||||
); | |||||
} | |||||
} |
@ -0,0 +1,112 @@ | |||||
import 'package:flutter/material.dart'; | |||||
import 'package:shared_preferences/shared_preferences.dart'; | |||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | |||||
import '/views/authentication/unauthenticated_landing.dart'; | |||||
class ConversationsList extends StatefulWidget { | |||||
const ConversationsList({Key? key}) : super(key: key); | |||||
@override | |||||
State<ConversationsList> createState() => _ConversationsListState(); | |||||
} | |||||
class _ConversationsListState extends State<ConversationsList> { | |||||
@override | |||||
void initState() { | |||||
checkLogin(); | |||||
super.initState(); | |||||
} | |||||
final _suggestions = <String>[]; | |||||
final _biggerFont = const TextStyle(fontSize: 18); | |||||
Future checkLogin() async { | |||||
SharedPreferences preferences = await SharedPreferences.getInstance(); | |||||
print(preferences.getBool('islogin')); | |||||
if (preferences.getBool('islogin') != true) { | |||||
setState(() { | |||||
Navigator.of(context).push(MaterialPageRoute( | |||||
builder: (context) => const UnauthenticatedLandingWidget(), | |||||
)); | |||||
}); | |||||
} | |||||
} | |||||
Widget list() { | |||||
if (_suggestions.isEmpty) { | |||||
return const Center( | |||||
child: Text('No Conversations'), | |||||
); | |||||
} | |||||
return ListView.builder( | |||||
itemCount: _suggestions.length, | |||||
padding: const EdgeInsets.all(16.0), | |||||
itemBuilder: /*1*/ (context, i) { | |||||
//if (i >= _suggestions.length) { | |||||
// TODO: Check for more conversations here. Remove the itemCount to use this section | |||||
//_suggestions.addAll(generateWordPairs().take(10)); /*4*/ | |||||
//} | |||||
return Column( | |||||
children: [ | |||||
ListTile( | |||||
title: Text( | |||||
_suggestions[i], | |||||
style: _biggerFont, | |||||
), | |||||
), | |||||
const Divider(), | |||||
] | |||||
); | |||||
}, | |||||
); | |||||
} | |||||
@override | |||||
Widget build(BuildContext context) { | |||||
return WillPopScope( | |||||
onWillPop: () async => false, | |||||
child: Scaffold( | |||||
appBar: AppBar( | |||||
title: Text('Envelope'), | |||||
actions: <Widget>[ | |||||
PopupMenuButton( | |||||
icon: const FaIcon(FontAwesomeIcons.ellipsisVertical, color: Colors.white, size: 40), | |||||
itemBuilder: (context) => [ | |||||
const PopupMenuItem<int>( | |||||
value: 0, | |||||
child: Text("Settings"), | |||||
), | |||||
const PopupMenuItem<int>( | |||||
value: 1, | |||||
child: Text("Logout"), | |||||
), | |||||
], | |||||
onSelected: (item) => selectedMenuItem(context, item), | |||||
), | |||||
], | |||||
), | |||||
body: list(), | |||||
), | |||||
); | |||||
} | |||||
void selectedMenuItem(BuildContext context, item) async { | |||||
switch (item) { | |||||
case 0: | |||||
print("Settings"); | |||||
break; | |||||
case 1: | |||||
SharedPreferences preferences = await SharedPreferences.getInstance(); | |||||
preferences.setBool('islogin', false); | |||||
Navigator.of(context).push(MaterialPageRoute( | |||||
builder: (context) => const UnauthenticatedLandingWidget(), | |||||
)); | |||||
break; | |||||
} | |||||
} | |||||
} |