Decrypting cognito codes with KMS client from aws-sdk-v3 - amazon-cognito

I am following this instruction to implement custom message sender in Cognito https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-sms-sender.html
All works well with similar code (I use Typescript on AWS Lambda):
import {buildClient, CommitmentPolicy, KmsKeyringNode} from '#aws-crypto/client-node';
import b64 from 'base64-js';
const {decrypt} = buildClient(CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT);
const keyring = new KmsKeyringNode({keyIds: ["my-key-arn"]});
...
const {plaintext} = await decrypt(keyring, b64.toByteArray(event.request.code));
console.log(plainttext.toString()) // prints plain text exactly as I need
However, this library #aws-crypto/client-node makes my bundle really huge, almost 20MB! Probably because it depends on some of older AWS libs...
I used to use modular libraries like #aws-sdk/xxx which indeed give much smaller bundles.
I have found that for encrypt/decrypt I can use #aws-sdk/client-kms. But it doesn't work!
I am trying the following code:
import {KMSClient, DecryptCommand} from "#aws-sdk/client-kms";
import b64 from 'base64-js';
const client = new KMSClient;
await client.send(new DecryptCommand({CiphertextBlob: b64.toByteArray(event.request.code), KeyId: 'my-key-arn'}))
Which gives me an error:
InvalidCiphertextException: UnknownError
at deserializeAws_json1_1InvalidCiphertextExceptionResponse (/projectdir/node_modules/#aws-sdk/client-kms/dist-cjs/protocols/Aws_json1_1.js:3157:23)
at deserializeAws_json1_1DecryptCommandError (/projectdir/node_modules/#aws-sdk/client-kms/dist-cjs/protocols/Aws_json1_1.js:850:25)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async /projectdir/node_modules/#aws-sdk/middleware-serde/dist-cjs/deserializerMiddleware.js:7:24
at async /projectdir/node_modules/#aws-sdk/middleware-signing/dist-cjs/middleware.js:14:20
at async StandardRetryStrategy.retry (/projectdir/node_modules/#aws-sdk/middleware-retry/dist-cjs/StandardRetryStrategy.js:51:46)
at async /projectdir/node_modules/#aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:6:22
at async REPL7:1:33 {
'$fault': 'client',
'$metadata': {
httpStatusCode: 400,
requestId: '<uuid>',
extendedRequestId: undefined,
cfId: undefined,
attempts: 1,
totalRetryDelay: 0
},
__type: 'InvalidCiphertextException'
}
What am I doing wrong? Does this KMSClient support what I need?
I have also tried AWS CLI aws kms decrypt --ciphertext-blob ... command, gives me exactly same response. Though if I encrypt and decrypt any random message like "hello world", it works like a charm.
What am I doing wrong and what is so special about Cognito code ciphertext so I have to decrypt it somehow another way?

Short answer: Cognito does not use KMS to encrypt the text, it uses the Encryption SDK. So you cannot use KMS to decrypt Cognito ciphertext.
Longer answer: I spent the past day trying to get a Python email-sender-trigger function working against Cognito using boto3 and the KMS client until I found another post (somewhere?) explaining that Cognito does not encrypt data using KMS, rather the Encryption SDK. Of course these two encryption mechanisms are not compatible.
For JavaScript and Node.js applications, it looks like you have an alternative to including the entire crypto-client: https://www.npmjs.com/package/#aws-crypto/decrypt-node
If all you are doing is decrypting, the above package will let you decrypt using the Encryption SDK and it's only 159KB.

I have managed to solve my task. I have realized that indeed it does not simply uses KMS to encrypt the text, the encryption/decryption process is much more complicated.
There is reference page https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/message-format.html
It describes how the message looks like, with all the headers and body, with IV, AAD, keys, etc... I have written my own script to parse it all and properly decrypt, it worked! Probably it's too long to share... I suggest to use the reference instead. Hopefully in future they will publish proper modular version of SDK.
The one from '#aws-crypto' didn't work for me, probably doesn't support all the protocols properly. This might be not the truth at the moment you are reading it.

Related

Is it Possible to Use AWS Amplify Without Using Amazon Cognito?

I have android and iOS apps. I need to allow them to download and upload assets on S3, and sometimes want to send data to mobile without having to use Amazon Cognito as it costs lot of money to have. I have secret and access keys, and I want mobile users to use these keys instead of having to use Amazon Cognito.
So is it possible to use AWS Amplify without Amazon Cognito ?
This answer only addresses iOS, but I’m guessing that you’ll be able to do something similar on Android. Just replace “protocol” with “interface” :-)
In order to use Amplify you need to give the SDK an AWSCredentialsProvider. Luckily, this is just a protocol, not a concrete class, and it’s straightforward to implement your own:
// MyCredentialsProvider.swift
import AWSCore
import Foundation
class MyCredentialsProvider: NSObject, AWSCredentialsProvider {
func credentials() -> AWSTask<AWSCredentials> {
let credentials = AWSCredentials(accessKey: "AAAAAAAAAAAAAAAAAAA",
secretKey: "zzzzzzzzzzzzzzzzzzz",
sessionKey: nil,
expiration: nil)
return AWSTask(result: credentials)
}
func invalidateCachedTemporaryCredentials() {
// I'm not entirely sure what this method is supposed to do, but it
// seems to be okay to leave it as a no-op.
}
}
You can use your class like this:
let provider: AWSCredentialsProvider = MyCredentialsProvider()
let serviceConfig = AWSServiceConfiguration(region: .USWest2,
credentialsProvider: provider)
AWSServiceManager.default().defaultServiceConfiguration = serviceConfig
// ...actually use AWS Amplify...
Of course, you would probably want to make your credentials provider hook into your own authentication system somehow, but this at least gives you an idea of how to pass your own access key and secret key to the SDK.
Yes, it's possible. You don't need user authentication to use AWS Amplify.

Expo / React Native Basic Authorization

I'm new to React native and Expo, but started to write my own app on it, with the same backend i used with my Cordova app.
Unfortunately i hit a roadblock trying to recreate the btoa() function from browsers, that i use to authenticate users with Basic authorization.
No matter what i try, i can't seem to get the same result as i did with btoa. I tried researching the subject, but i can't find a solid answer what's the difference between Base64.encode() and btoa().
I know i'm doing something wrong. When i try out the post request with Postman, i get the correct Basic auth token with it. But when i do it in code with base64 encoding(tried multiple libraries), the result differs.
Example:
test#test.com:asdasd
in postman: "dGVzdEB0ZXN0LmNvbTphc2Rhc2Rhc2Q="
in app(to utf8, then base64): "W29iamVjdCBBcnJheUJ1ZmZlcl0="
Relevant part of my code:
const utf8_enc = utf8.encode(email+':'+password);
const b64_enc = base64.encode(utf8_enc);
console.log(b64_enc);
Used libraries:
Base64- https://www.npmjs.com/package/base-64
UTF8 - https://github.com/mathiasbynens/utf8.js
Please tell me why are the two different, and how can i recreate the Postman version.
Thank you!
Ok, I see what's happening now. If you follow the docs for that utf8 package, it won't import correctly in React Native. You can see that it's not imported correctly by trying to access decode() or version as both will give you undefined. I think the reason is because they don't support es2015 modules (see this rejected PR). This package will however work fine in Node.js or in the browser.
Oddly enough, you do have access to encode() when you import. It just doesn't do what you think it does. When you attempt to use encode(), all it actually returns is the string: [object ArrayBuffer]. In fact, no matter what string you pass to it, it'll always return the same result. Now if you use btoa() on this string (with or without UTF-8 conversion since there's no difference in this case), you will see that you get that same output in the browser: W29iamVjdCBBcnJheUJ1ZmZlcl0=
So, how to get around this?
If all you expect are extended ASCII strings, then you don't need to encode it in UTF-8 as they will all be within the valid character set. So you can just do:
base64.encode(email+':'+password);
However, if you anticipate supporting all Unicode characters, then you have a few options to convert that string:
Fork the utf8 package to have it support modules/exporting.
Copy paste the entirety of the utf8 source and put it in your own local library and export the functions.
Write your own UTF-8 encoder/decoder using the method suggested here which itself is from the MDN Documentation.
So there's a reference to a solution, here is the relevant encode part of the code from the MDN documentation turned into a function:
function utf8encode(str) {
return encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(
match,
p1
) {
return String.fromCharCode(parseInt(p1, 16));
});
}

React native RSA encryption with a public PEM or SSLeay key

I am trying to find a working javascript library for react-native that will allow RSA public key encryption in PEM format.
I am not an expert in encryption and just starting out with react-native so please excuse me if i happened to miss something obvious.
So far i have tryed
react-native-rsa as recomended by this post
and
react-native-rsa-util.
I couldn't get react-native-rsa-util to work and react-native-rsa doesn't seem to accept public PEM keys but only keys in the JWK format.
The only reason i am not willing to use the JWK format is that i cannot find a PHP library that will decrypt the incoming message with a JWK key.
I would highly appreciate any help / pointers.
Thanks alot
I wrote a PHP library that supports JWT encryption/decryption with a lot of other useful features including compression.
All algorithms referenced in the RFC7518 and JWK/JWKSet are supported.
What you can do is convert your PEM key into JWK with my library:
<?php
use Jose\Factory\JWKFactory;
$jwk = JWKFactory::createFromKeyFile('/path/to/my/key.pem');
var_dump($jwk->getAll());
And to decrypt a JWT with your JWK and my library:
use Jose\Loader;
$input = 'eyJhbGciOiJS...';
$loader = new Loader();
$jwe = $loader->loadAndDecryptUsingKey($input, $jwk, ['RSA-OAEP-256'], ['A256CBC-HS512']); // The list of accepted key and content encryption algorithms depends on your needs
The variable $jwe is now JWE Object.
You can get the payload by calling $jwe->getPayload();
Do not hesitate to contact me on the dedicated Gitter channel if needed.

Does Fineuploader support Server-Side Encryption with Customer-Provided Encryption Keys

We recently started using Fineuploader and got it working with server side encryption. But we now want to use "Server-Side Encryption with Customer-Provided Encryption Keys" documentation can be found here. I searched the API and checked the blog and couldn't find anything about it.
Does Fineuploader support this?
If it does how do I implement it?
You should be able to set x-amz-server-side​-encryption​-customer-algorithm, x-amz-server-side​-encryption​-customer-key, and x-amz-server-side​-encryption​-customer-key-MD5 using the request.params option, and setParams() method.
When you add these headers, make sure to leave off the x-amz- prefix as Fine Uploader will add this for you.
request.params = {
"server-side-encryption-customer-algorithm": "foo",
"server-side​-encryption​-customer-key": "bar",
"server-side​-encryption​-customer-key-MD5": "baz",
}

How to access Firefox Sync bookmarks without Firefox

Firefox 4 syncs bookmarks and other settings to a host run by mozilla.
How do I access my bookmarks there (without Firefox)?
Is there a documented API?
It seems https://developer.mozilla.org/en/Firefox_Sync should contain the neccessary documentation but all links except the first point to empty pages.
I found a script called weave.py here https://github.com/mozilla/weaveclient-python/blob/master/weave.py that is supposed to be able to access those bookmarks but it is unable to use my credentials. It seems to expect usernames without "#" characters.
Is there any documentation out there on how to access Firefox sync data. Preferably with examples.
Right now I don't even know the entry point to this supposed web service.
When I go to https://services.mozilla.com/ I can change my password and presumably remove everything.
If you look at https://wiki.mozilla.org/Services/Sync, I think that's the documentation you want. More detail is at https://wiki.mozilla.org/Labs/Weave/Sync/1.1/API.
Indeed, the username is sha1 + base32. Python code:
import base64
import hashlib
base64.b32encode(hashlib.sha1('myemail#gmail.com').digest()).lower()
The WeaveID returned by ID.get("WeaveID").username is indeed SHA-1 hashed and base32 encoded.
A nice way to do this in Java is to use Apache Commons Codec, which includes Base32 since version 1.5:
public String getWeaveID(String email) throws UnsupportedEncodingException
{
byte[] sha = DigestUtils.sha(email.getBytes("UTF-8"));
Base32 b32 = new Base32(64, new byte[]{ }, false);
return b32.encodeToString(sha).toLowerCase();
}