Off-chain encryption using public key - encryption

I am trying to sign and encrypt a metadata using a user's public key before uploading it to the blockchain, and decrypt the data with the user's private key off-chain. I have followed this and it worked perfectly but it is asking for the user's private key of the currently connected user in Metamask.
So I tried following the tutorial in Metamask's documentation [1]. And this is the code:
await window.ethereum.enable();
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
let encryptionPublicKey;
ethereum
.request({
method: 'eth_getEncryptionPublicKey',
params: [accounts[0]], // you must have access to the specified account
})
.then((result) => {
console.log(result);
encryptionPublicKey = result;
})
.catch((error) => {
if (error.code === 4001) {
// EIP-1193 userRejectedRequest error
console.log("We can't encrypt anything without the key.");
} else {
console.error(error);
}
});
console.log(encryptionPublicKey);
const encryptedMessage = ethUtil.bufferToHex(
Buffer.from(
JSON.stringify(
sigUtil.encrypt({
publicKey: encryptionPublicKey,
data: 'hello world!',
version: 'x25519-xsalsa20-poly1305',
})
),
'utf8'
)
);
I'm getting an empty result, thus an empty encryptionPublicKey. Hence, I am getting this error:
Uncaught (in promise) Error: Missing publicKey parameter
How can I fix this? Any help is greatly appreciated.
Similar Question on encrypting data using public key but there is no clear answer:
[1]

I managed to get it working by putting await before ethereum.request.
Additional Question: Does this encryption and decryption method works in creating and retrieving transactions in blockchain?

Related

Firebase cloud function error: Maximum call size stack size exceeded

I've made firebase cloud function which adds the claim to a user that he or she has paid (set paid to true for user):
const admin = require("firebase-admin");
exports.addPaidClaim = functions.https.onCall(async (data, context) => {
// add custom claim (paid)
return admin.auth().setCustomUserClaims(data.uid, {
paid: true,
}).then(() => {
return {
message: `Succes! ${data.email} has paid for the course`,
};
}).catch((err) => {
return err;
});
});
However, when I'm running this function: I'm receiving the following error: "Unhandled Rejection (RangeError): Maximum call stack size exceeded". I really don't understand why this is happening. Does somebody see what could cause what's getting recalled which in turn causes the function to never end?
Asynchronous operations need to return a promise as stated in the documentation. Therefore, Cloud Functions is trying to serialize the data contained by promise returned by transaction, then send it in JSON format to the client. I believe your setCustomClaims does not send any object to consider it as an answer to the promise to finish the process so it keeps in a waiting loop that throws the Range Error.
To avoid this error I can think of two different options:
Add a paid parameter to be able to send a JSON response (and remove the setCustomUserClaim if it there isn’t any need to change the user access control because they are not designed to store additional data) .
Insert a promise that resolves and sends any needed information to the client. Something like:
return new Promise(function(resolve, reject) {
request({
url: URL,
method: "POST",
json: true,
body: queryJSON //A json variable I've built previously
}, function (error, response, body) {
if (error) {
reject(error);
}
else {
resolve(body)
}
});
});

Migrating salted sha512 passwords from symfony 2 to firebase authentication

I am trying to migrate users (including passwords) from an old symfony 2 application to firebase authentication (or google identity platform).
In the symfony2 application the passwords of the users are hashed using sha512 with a salt. I already found that users can be imported using their password and hash in the documentation of firebase (https://firebase.google.com/docs/auth/admin/import-users). However it seems like the sha512 hashing that is used by firebase is not the same as was used by symfony.
For the old symfony project the following configuration is used:
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
By looking into the source I found that symfony given a salt and a password symfony will produce the hash like this: (in python code)
def get_hash(salt, password):
hash = password.encode('utf-8')
salted = hash + salt
hash = hashlib.sha512(salted).digest()
for i in range(1, 5000):
# symfony keeps adding salted for every iteration, this is something firebase does not it seems
hash = hashlib.sha512(hash + salted).digest()
return base64.b64encode(hash).decode('utf-8')
However this code does not allow me to login when i import it like in the code below. It however does produce the same hash as I have in my database of the symfony2 application:
app = firebase_admin.initialize_app()
salt = '{test}'.encode('utf-8')
hash = get_hash(salt=salt, password='xyz')
print('calculated hash', base64.b64encode(hash))
users = [
auth.ImportUserRecord(
uid='foobar',
email='foo#bar.com',
password_hash=hash,
password_salt=salt
)
]
hash_alg = auth.UserImportHash.sha512(rounds=5000)
try:
result = auth.import_users(users, hash_alg=hash_alg)
for err in result.errors:
print('Failed to import user:', err.reason)
except exceptions.FirebaseError as error:
print('Error importing users:', error)
I can however login with the password when i use the following fuction.
def get_hash(salt, password):
hash = password.encode('utf-8')
salted = salt + hash
hash = hashlib.sha512(salted).digest()
for i in range(1, 5000):
hash = hashlib.sha512(hash).digest()
return hash
I have already found a way to change the order of adding the salt but i can find no way to hash like this in firebase hash = hashlib.sha512(hash + salted).digest().
Now it seems like there is no way to migrate my password to firebase as the implementation of symfony is a bit different from the one used by firebase. Does anyone know a way to make sure I can still import my current hashes? This would be great.
If not, what would be alternative work arounds?
Is it possible to let firebase do a request to my own endpoint to verify password.
Another way would be to try to catch the signin process and send it to my own endpoint first, set the password in the background and then send the request to firebase?
You haven't specified what your client application is using, so I'm just going to assume it's a web application that will use the Firebase Web SDK.
To use this solution, you'll need to migrate the Symfony user data to Firestore under a private _migratedSymfonyUsers collection, where each document is the email of that user.
On the client, the process will be:
Collect email and password from the user
Attempt to sign in to Firebase with that email and password combination
If that failed, invoke a Callable Cloud Function with that email and password combination.
If function returned a success message (see below), reattempt signing in the user with the given email and password
Handle success/errors as appropriate
On the client, this would look like:
const legacySignIn = firebase.functions().httpsCallable('legacySignIn');
async function doSignIn(email, password) {
try {
return await firebase.auth()
.signInWithEmailAndPassword(email, password);
} catch (fbError) {
if (fbError.code !== "auth/user-not-found")
return Promise.reject(fbError);
}
// if here, attempt legacy sign in
const response = await legacySignIn({ email, password });
// if here, migrated successfully
return firebase.auth()
.signInWithEmailAndPassword(email, password);
}
// usage:
doSignIn(email, password)
.then(() => console.log('successfully logged in/migrated'))
.catch((err) => console.error('failed to log in', err));
In the Callable Cloud Function:
(optional) Assert that the request is coming from your application with App Check
Assert email and password were provided and throw error if not.
Assert that the email given exists in your migrated users and throw an error if not.
If in migrated users, hash the password and compare against the stored hash.
Throw an error if hashes don't match.
If hashes match, create a new Firebase user with that email and password combination
Once created, delete the migrated hash and return success message to the caller
On the server, this would look like:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
function symfonyHash(pwd, salt) {
// TODO: Hash function
return /* calculatedHash */;
}
exports.legacySignIn = functions.https.onCall(async (data, context) => {
if (context.app == undefined) { // OPTIONAL
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called from an App Check verified app.');
}
if (!data.email || !data.password) {
throw new functions.https.HttpsError(
'invalid-argument',
'An email-password combination is required');
}
if (data.email.indexOf("/") > -1) {
throw new functions.https.HttpsError(
'invalid-argument',
'Email contains forbidden character "/"');
}
const migratedUserSnapshot = await admin.firestore()
.doc(`_migratedSymfonyUsers/${data.email}`);
if (!migratedUserSnapshot.exists) {
throw new functions.https.HttpsError(
'not-found',
'No user matching that email address was found');
}
const storedHash = migratedUserSnapshot.get("hash");
const calculatedHash = symfonyHash(password, salt);
if (storedHash !== calculatedHash) {
throw new functions.https.HttpsError(
'permission-denied',
'Given credential combination doesn\'t match');
}
// if here, stored and calculated hashes match, migrate user
// get migrated user data
const { displayName, roles } = migratedUserSnapshot.data();
// create the user based on migrated data
const newUser = await admin.auth().createUser({
email,
password,
...(displayName ? { displayName } : {})
});
if (roles) { // <- OPTIONAL
const roleMap = {
"symfonyRole": "tokenRole",
"USERS_ADMIN": "isAdmin",
// ...
}
const newUserRoles = [];
roles.forEach(symfonyRole => {
if (roleMap[symfonyRole]) {
newUserRoles.push(roleMap[symfonyRole]);
}
});
if (newUserRoles.length > 0) {
// migrate roles to user's token
await setCustomUserClaims(
newUser.uid,
newUserRoles.reduce((acc, r) => { ...acc, [r]: true }, {})
);
}
}
// remove the old user data now that we're done with it.
await hashSnapshot.ref.delete();
// return success to client
return { success: true };
});

Firebase with Google Cloud Function - Realtime Database Validation to check username not already taken

I have found various other posts that all seem to make me believe that my implementation should be working but something is still missing.
Here is my Firebase Realtime database rules:
"usernames": {
".read" : true,
".write" : "auth !== null",
"$username":{
".validate": "!root.child('usernames').hasChild($username)"
}
}
Inside my usernames node i have for example:
"usernames"
|---"johnDoe123" : "XrG34odsfla82343174094389"
|--- etc.....
I have a cloud function that I am calling by passing a token and when the cloud function runs it attempts to update the "usernames" node by adding the new requested username assuming the username is not yet taken.
Here is the portion of the function that I am using that writes to the Firebase usernames node:
let newUser = {}
newUser[req.query.username] = decoded.uid
admin.database()
.ref('usernames')
.update(newUser)
.then(() => {
console.log(`New username <${req.query.username}> added`)
res.status(200).send(decoded)
}).catch(e=>{
console.log('Error', e)
})
The cloud function is updating the usernames node but my problem is that I need to have it reject the update with the validation if another child node has the same username. As of now my rules continue to allow the update to occur despite having another child node with the same exact username which is associated to a different uid.
Any ideas what I'm doing wrong?
Not sure if this is the best way, but it works for my purpose which is to check the first time a user is getting setup within my app to determine if the username being requested is already taken. If so it sends back JSON response that it was already taken. If it was not taken then it creates the record and then sends back JSON response of success..
module.exports = functions.https.onRequest((req, res) => {
cors(req, res, () => {
const tokenId = req.get('Authorization').split('Bearer ')[1];
return admin.auth().verifyIdToken(tokenId)
.then((decoded) => {
admin.database()
.ref('usernames')
.child(req.query.username)
.once('value', function(snapshot) {
if(snapshot.val() === null){
let newUser = {}
newUser[req.query.username] = decoded.uid
admin.database()
.ref('usernames')
.update(newUser)
.then(() => {
console.log(`New username <${req.query.username}> added`)
res.status(200).send({
success: true,
message: "Username created"
})
}).catch(err=>{
res.status(500).send(err)
})
}
else{
res.status(200).send({
success: false,
message: "Username already taken"
})
}
});
})
.catch((err) => {
res.status(401).send(err)
});
})
});
If you use the Firebase Authentication to register all users, then this system will tell the user automatically that the email address they have used to register with is already an account.
You could also make the username part of the tree path /users/AUserName so it would return null when queried

What is a method of using Firebase Cloud Functions in Flutter.

I have been searching all over on how to implement firebase functions with a flutter application. It does not seem like there is an SDK available (yet). I've also tried adding the gradle dependency implementation 'com.google.firebase:firebase-functions:15.0.0' to my app/build.gradle but this causes build errors.
Has anyone done an implementation that works? I am unable to find any documentation on how to handle credentials and the transport of data in order to build my own firebase functions call.
I have created a rough outline of how I am thinking this is intended to work, but may be way off base.
Future<dynamic> updateProfile(String uid, AccountMasterViewModel avm) async {
Uri uri = Uri.parse(finalizeProfileFunctionURL);
var httpClient = new HttpClient();
String _result = '';
try {
return await httpClient
.postUrl(uri)
.then((HttpClientRequest request) {
return request.close();
// authentication??
// Fields and data??
})
.then((HttpClientResponse response) async {
print(response.transform(new Utf8Codec().decoder).join());
if (response.statusCode == HttpStatus.OK) {
String json = await response.transform(new Utf8Codec().decoder).join();
_result = jsonDecode(json);
// Do some work
return json;
}
else {
return ':\nHttp status ${response.statusCode}';
}
});
}
catch (exception) {
return 'Failed ' + exception.toString();
}
}
I'd like to be able to send an object, like
{
accountID: src.accountID,
accountName: src.name,
accountImg: src.image
}
and then handle the response. But as I said, I can't find any working examples or tutorials on how to do this. It's fairly simple to do this client size and talk directly to the database, however, there are validations and data components that need to be hidden from the client, so cloud functions is the way I would like to do this.
Yes, there is a cloud_function package available here: https://pub.dartlang.org/packages/cloud_function.
so as to make a call to the function you can just call
CloudFunctions.instance.call(
functionName: 'yourCloudFunction',
parameters: <String, dynamic>{
'param1': 'this is just a test',
'param2': 'hi there',
},
);
An updated answer to calling Firebase's Cloud Functions in Flutter would be
var callable = CloudFunctions.instance.getHttpsCallable(functionName: 'functionName'); // replace 'functionName' with the name of your function
dynamic response = callable.call(<String, dynamic>{
'param1': param1 //replace param1 with the name of the parameter in the Cloud Function and the value you want to insert
}).catchError((onError) {
//Handle your error here if the function failed
});
This is a good tutorial on cloud functions in flutter which helped me:
https://rominirani.com/tutorial-flutter-app-powered-by-google-cloud-functions-3eab0df5f957
Cloud functions can be triggered by data change triggers in the realtime database, Firestore, or Datastore, as well as authentication triggers.
You could just persist
{
accountID: src.accountID,
accountName: src.name,
accountImg: src.image
}
to the database and register a trigger that runs a Cloud Function when data at a specific path is inserted, updated, or deleted.
https://firebase.google.com/docs/functions/firestore-events

Github OAuth using Firebase - how to get user's username

I followed the Firebase's guide on how to authenticate with Github. https://firebase.google.com/docs/auth/web/github-auth
The return result from Firebase's signInWithRedirect method contains the user's displayName and email, etc. However, it doesn't seem to contain user's 'login' username which is the key for invoking most of Github's API calls.
I am sure there is a way to get it, but I just can't seem to find any documentation. Does anyone happen to know how to solve it?
I ended up using Github's API to get user's username with accessToken.
You should be able to get the user's GitHub username through a parameter called "username" (see more here: https://github.com/firebase/firebase-simple-login/blob/master/docs/v1/providers/github.md)
Note: firebase-simple-login was deprecated on October 3th, 2014
You can use get the authenticated user from this GitHub's api
Or if you use octokit javascript rest api client, you can do something like this
octokit = new Octokit({auth: userAccessToken })
octokit.users.getAuthenticated()
.then(result => {
console.log(result.data.login) // this is the username
})
Note: you'll get accessToken after GitHub <-> firebase login
Hope this is helpful!
You can get the username in additionalUserInfo:
const githubProvider = new firebaseClient.auth.GithubAuthProvider();
githubProvider.addScope('read:user');
githubProvider.setCustomParameters({
allow_signup: false,
});
firebaseClient.initializeApp(clientConfig);
async function submit() {
try {
const response = await firebaseClient
.auth()
.signInWithPopup(githubProvider);
console.log(response.additionalUserInfo);
} catch (error) {
alert(error);
}
}
You Can use email to do authorized requests insted username:
Username: mayGitHubEmail#mail.com
Password: accessToken
like this with Postman
body sent
Here is a sample using class func in Swift using Alamofire and SwiftyJSON pods:
import Alamofire
import SwiftyJSON
enum NetworkError: Error {
case url
case server
case auth
}
class GistServices {
class func makePostApiCall(toUrl path: String, withBody parameters: JSON, usingCredentials: Bool = false) -> Result<Data?, NetworkError> {
guard let url = URL(string: path) else {
return .failure(.url)
}
if let email = UserAuthSingleton.shared.get(), let password = UserAuthSingleton.shared.getUserToken() {
var result: Result<Data?, NetworkError>!
var request = AF.request(url, method: .post, parameters: parameters)
if(usingCredentials){
let credentialData = "\(email):\(password)".data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
let base64Credentials = credentialData.base64EncodedString()
let headers = [HTTPHeader(name: "Authorization", value: "Basic \(base64Credentials)"),
HTTPHeader(name: "Accept", value: "application/json"),
HTTPHeader(name: "Content-Type", value: "application/json")]
request = AF.request(url, method: .post, parameters: parameters.dictionaryValue, encoder: JSONParameterEncoder.default, headers: HTTPHeaders(headers))
}
request
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.response { (response) in
switch response.result {
case .failure(_):
result = .failure(.server)
case .success(let value):
result = .success(value)
}
}
return result
}
return .failure(.auth)
}
}

Resources