How do I create a JWK from PFX certificate? - .net-core-3.1

Background: I'm trying to create a JWK from a PFX file so that I'm able to use the Okta SDK.
The OktaClient expects the private key in the form of a JWK. An example I stole from their unit tests looks like.
{
"p": "{{lots_of_characters}}",
"kty": "RSA",
"q": "{{lots_of_characters}}",
"d": "{{lots_of_characters}}",
"e": "AQAB",
"kid": "3d3062f5-16a4-42b5-837b-19b6ef1a0edc",
"qi": "{{lots_of_characters}}",
"dp": "{{lots_of_characters}}",
"dq": "{{lots_of_characters}}",
"n": "{{lots_of_characters}}"
}
Everything I've tried results in the exception "Something went wrong when creating the signed JWT. Verify your private key." I believe this is because I'm losing the private key part of the cert when I use the IdentityModel convert method (noted below).
var signingCert = new X509Certificate2("{{my_cert}}.pfx", "{{my_passphrase}}");
var privateKey = signingCert.GetRSAPrivateKey();
var rsaSecurityKey = new RsaSecurityKey(privateKey);
// The "HasPrivateKey" flag is suddenly false on the resulting object from this method
var rsaJwk = JsonWebKeyConvert.ConvertFromRSASecurityKey(rsaSecurityKey);
var rsaJwkSerialized = JsonSerializer.Serialize(rsaJwk);
var oktaClientConfig = new OktaClientConfiguration
{
OktaDomain = "{{my_okta_domain}}",
ClientId = {{my_client_id}},
AuthorizationMode = AuthorizationMode.PrivateKey,
PrivateKey = new JsonWebKeyConfiguration(rsaJwkSerialized);,
Scopes = new List<string> {"okta.users.manage"}
};
var oktaClient = new OktaClient(oktaClientConfig);
// This throws when trying to self-sign the JWT using my private key
var oktaUsers = await oktaClient.Users.ListUsers().ToArrayAsync();

Well, after days of trying to figure this out, found it mere hours after finally posting on SO.
It turns out there are flags you set when you create the X509Certificate2 that can tell the cert that it is exportable and this is required for the JsonWebKeyConverter to properly create the JWK.
var signingCert = new X509Certificate2("{{my_cert}}.pfx", "{{my_passphrase}}", X509KeyStorageFlags.Exportable);

Related

Why is the public key in `.near-credentials` keypairs seemingly irrelevant?

Let's say I create an account foo.near on NEAR with a single full access public key ed25519:publiccc which is matched to a private key ed25519:secrettt and stored locally in the .near-credentials/mainnet/foo.near.json file.
I can now send myself 1 NEAR using near send foo.near foo.near 1 to verify the keys work. If I modify the private key in that keypair at all, the send fails. Good.
Then I replace the public key in that keypair with a nonsense string like ed25519:giggle.
I can still send myself 1 NEAR using the same command. Why?
It appears the public key is totally irrelevant in this keypair. You can use a nonsense string or a public key that's supposed to have limited access only and the private key gives full access to transfer funds.
Good question! I had to do some digging. So let's start with what the CLI is doing behind the scenes. The code is found here:
exports.sendMoney = async function (options) {
await checkCredentials(options.sender, options.networkId, options.keyStore);
console.log(`Sending ${options.amount} NEAR to ${options.receiver} from ${options.sender}`);
const near = await connect(options);
const account = await near.account(options.sender);
const result = await account.sendMoney(options.receiver, utils.format.parseNearAmount(options.amount));
inspectResponse.prettyPrintResponse(result, options);
};
Essentially it connects to the network, creates an account object, and then calls the sendMoney method for that account object which is through near-api-js. The way it's instantiated is through near.account() which behind the scenes does the following (found here):
async account(accountId: string): Promise<Account> {
const account = new Account(this.connection, accountId);
return account;
}
This is just calling the class' constructor passing in the connection and account ID. Up until this point, we have no reference to the private or public key other than the connection which has a keystore indicating WHERE the keypair will be found. Ok moving on.
After the account class is initialized, the sendMoney method is called and the only action is a simple transfer with the amount. The code for this method can be seen here.
async sendMoney(receiverId: string, amount: BN): Promise<FinalExecutionOutcome> {
return this.signAndSendTransaction({
receiverId,
actions: [transfer(amount)]
});
}
You'll notice that it calls the signAndSendTransaction method that is part of the Account Class (found here).
async signAndSendTransaction({ receiverId, actions, returnError }: SignAndSendTransactionOptions): Promise<FinalExecutionOutcome> {
let txHash, signedTx;
// TODO: TX_NONCE (different constants for different uses of exponentialBackoff?)
const result = await exponentialBackoff(TX_NONCE_RETRY_WAIT, TX_NONCE_RETRY_NUMBER, TX_NONCE_RETRY_WAIT_BACKOFF, async () => {
[txHash, signedTx] = await this.signTransaction(receiverId, actions);
const publicKey = signedTx.transaction.publicKey;
try {
return await this.connection.provider.sendTransaction(signedTx);
}
...
...
Out of this method, the important thing is this.signTransaction whose method is found here:
protected async signTransaction(receiverId: string, actions: Action[]): Promise<[Uint8Array, SignedTransaction]> {
const accessKeyInfo = await this.findAccessKey(receiverId, actions);
if (!accessKeyInfo) {
throw new TypedError(`Can not sign transactions for account ${this.accountId} on network ${this.connection.networkId}, no matching key pair exists for this account`, 'KeyNotFound');
}
const { accessKey } = accessKeyInfo;
const block = await this.connection.provider.block({ finality: 'final' });
const blockHash = block.header.hash;
const nonce = accessKey.nonce.add(new BN(1));
return await signTransaction(
receiverId, nonce, actions, baseDecode(blockHash), this.connection.signer, this.accountId, this.connection.networkId
);
}
In this method, there's a sneaky this.findAccessKey being done whose code can be found here.
async findAccessKey(receiverId: string, actions: Action[]): Promise<{ publicKey: PublicKey; accessKey: AccessKeyView }> {
// TODO: Find matching access key based on transaction (i.e. receiverId and actions)
const publicKey = await this.connection.signer.getPublicKey(this.accountId, this.connection.networkId);
if (!publicKey) {
throw new TypedError(`no matching key pair found in ${this.connection.signer}`, 'PublicKeyNotFound');
}
const cachedAccessKey = this.accessKeyByPublicKeyCache[publicKey.toString()];
if (cachedAccessKey !== undefined) {
return { publicKey, accessKey: cachedAccessKey };
}
try {
const rawAccessKey = await this.connection.provider.query<AccessKeyViewRaw>({
request_type: 'view_access_key',
account_id: this.accountId,
public_key: publicKey.toString(),
finality: 'optimistic'
});
// store nonce as BN to preserve precision on big number
const accessKey = {
...rawAccessKey,
nonce: new BN(rawAccessKey.nonce),
};
// this function can be called multiple times and retrieve the same access key
// this checks to see if the access key was already retrieved and cached while
// the above network call was in flight. To keep nonce values in line, we return
// the cached access key.
if (this.accessKeyByPublicKeyCache[publicKey.toString()]) {
return { publicKey, accessKey: this.accessKeyByPublicKeyCache[publicKey.toString()] };
}
this.accessKeyByPublicKeyCache[publicKey.toString()] = accessKey;
return { publicKey, accessKey };
} catch (e) {
if (e.type == 'AccessKeyDoesNotExist') {
return null;
}
throw e;
}
}
If you take a look at this code, you'll notice a really important line:
const publicKey = await this.connection.signer.getPublicKey(this.accountId, this.connection.networkId);
What is this returning? The short answer is that if you're using the NEAR-CLI, the connection.signer comes from UnencryptedFileSystemKeyStore since your keys are stored in .near-credentials. Now let's look at this getPublicKey method found here:
async getPublicKey(accountId?: string, networkId?: string): Promise<PublicKey> {
const keyPair = await this.keyStore.getKey(networkId, accountId);
if (keyPair === null) {
return null;
}
return keyPair.getPublicKey();
}
It does this.keyStore.getKey where the keyStore is the UnencryptedFileSystemKeyStore I mentioned earlier. The getKey method is then found here:
async getKey(networkId: string, accountId: string): Promise<KeyPair> {
// Find key / account id.
if (!await exists(this.getKeyFilePath(networkId, accountId))) {
return null;
}
const accountKeyPair = await readKeyFile(this.getKeyFilePath(networkId, accountId));
return accountKeyPair[1];
}
In this function, the final culprit is the readKeyFile where it returns the key pair (both public and private). This can be found here:
export async function readKeyFile(filename: string): Promise<[string, KeyPair]> {
const accountInfo = await loadJsonFile(filename);
// The private key might be in private_key or secret_key field.
let privateKey = accountInfo.private_key;
if (!privateKey && accountInfo.secret_key) {
privateKey = accountInfo.secret_key;
}
return [accountInfo.account_id, KeyPair.fromString(privateKey)];
}
Notice that it returns a KeyPair that is generated from the private key. This means that no matter what the public key is, it will overwrite it. I did a quick test where I console logged the accountInfo and it indeed returned the incorrect public key that I had set manually but when it generates the KeyPair from the private key, that information is overwritten.
TLDR:
When the your account signs a transaction, the public key is re-generated based on the private key. It doesn't matter what public key you have in your .near-credentials since it is overwritten when the KeyPair is created based on the private key.

Converting _bn back into PublicKey with Solana

When creating a Solana Transaction I set the feePayer with a public key. When I send this transaction between various endpoints, the feePayer gets converted to something like below:
"feePayer": {
"_bn": {
"negative": 0,
"words": [
37883239,
7439402,
52491380,
11153292,
7903486,
65863299,
41062795,
11403443,
13257012,
320410,
0
],
"length": 10,
"red": null
}
}
My question is, how can I convert this feePayer JSON object back as a PublicKey?
I've tried
new solanaWeb3.PublicKey(feePayer) or
new solanaWeb3.PublicKey(feePayer._bn)
However both don't seem to work, any ideas how to get this json form back into PublicKey: BN<....>?
A BN is a BigNumber. I had a similar case:
"feePayer": {
"_bn": "xcdcasaldkjalsd...."
}
Solution to it:
import BN from "bn.js";
const publicKeyFromBn = (feePayer) => {
const bigNumber = new BN(feePayer._bn, 16)
const decoded = { _bn: bigNumber };
return new PublicKey(decoded);
};
You should play with new BN(feePayer._bn, 16) params to make it work specifically for your case.

near-api-js signed contract deploy

I am having trouble getting serverside near-api-js account creation and contract deployment working successfully and would appreciate some input.
I am using a unencrypted local keystore set up as follows;
const privatekey = "__private key__";
const keyPair = KeyPair.fromString(privatekey); // generate key pair
const keyStore = new nearAPI.keyStores.UnencryptedFileSystemKeyStore(credentialsPath); // declare keystore type
const config = Object.assign(require('./config')('testnet'), {
networkId: networkId,
deps: { keyStore },
}); // setup config for near connection with keystore added
// update keystore with main account
await keyStore.setKey('testnet', config.masterAccount, nearAPI.utils.KeyPair.fromString(privatekey));
// connect near with config details
const near = await nearAPI.connect(config);
// load master account
const account = await new nearAPI.Account(near.connection, config.masterAccount);
I am creating a subaccount as;
const amountInYocto = utils.format.parseNearAmount("5");
// generate new public key
const newPublicKey = await near.connection.signer.createKey(newAccountName,config.networkId);
let keyPair;
let publicKey;
keyPair = await KeyPair.fromRandom('ed25519');
publicKey = keyPair.getPublicKey();
await near.connection.signer.keyStore.setKey(near.connection.networkId, newAccountName, keyPair);
const response = await near.createAccount(newAccountName, publicKey, amountInYocto);
When I try to deploy a contract to the created account I get the error;
Actor 'master account' doesn't have permission to account 'subaccount' to complete the action
This is how I deploy a contract:
const txs = [transactions.deployContract(fs.readFileSync(`../near/out/out.wasm`))];
const result = await account.signAndSendTransaction({
receiverId: subaccount,
actions: txs
});
Im sure I have something wrong in how i manage the keys, but cannot work it out. Any suggestions?

AVCapturePhotoSettings not accepting accept NSDictionary element

not sure what I am doing wrong, I wanna create a simple custom camera, I'm creating the AVCapturePhotoOutput attaching it to AVCaptureSession, then creating AVCapturePhotoSettings with minimum settings to make taking a picture work, see code below.
I get exception kCVPixelBufferPixelFormatTypeKey is not being define but it is indeed in the NSDictionary I am passing.
I need some light here, thanks
public void TakePicture()
{
var output = new AVCapturePhotoOutput();
_captureSession.AddOutput(output);
var settings = AVCapturePhotoSettings.Create();
var previewPixelType = settings.AvailablePreviewPhotoPixelFormatTypes.First();
var keys = new[]
{
new NSString("kCVPixelBufferPixelFormatTypeKey"),
new NSString("kCVPixelBufferWidthKey"),
new NSString("kCVPixelBufferHeightKey"),
};
var objects = new NSObject[]
{
// don't have to be strings... can be any NSObject.
previewPixelType,
new NSString("160"),
new NSString("160")
};
var dicionary = new NSDictionary<NSString, NSObject>(keys, objects);
settings.PreviewPhotoFormat = dicionary;
output.CapturePhoto(settings,this);
}
It is because kCVPixelBufferPixelFormatTypeKey is not available in Xamarin.
We should use CVPixelBuffer.PixelFormatTypeKey here . It will be convert to kCVPixelBufferPixelFormatTypeKey automatically when compiling.
The same reason for kCVPixelBufferWidthKey and kCVPixelBufferHeightKey , the api is CVPixelBuffer.WidthKey and CVPixelBuffer.HeightKey in Xamarin.iOS.

My Azure Storage output blob never shows up

I'm trying to build a simple Azure Function that is based on the ImageResizer example, but uses the Microsoft Cognitive Server Computer Vision API to do the resize.
I have working code for the Computer Vision API which I have ported into the Azure function.
It all seems to work ok (no errors) but my output blob never gets saved or shows up in the storage container. Not sure what I'm doing wrong as there are no errors to work with.
My CSX (C# function code) is as follows
using System;
using System.Text;
using System.Net.Http;
using System.Net.Http.Headers;
public static void Run(Stream original, Stream thumb, TraceWriter log)
{
//log.Verbose($"C# Blob trigger function processed: {myBlob}. Dimensions");
string _apiKey = "PutYourComputerVisionApiKeyHere";
string _apiUrlBase = "https://api.projectoxford.ai/vision/v1.0/generateThumbnail";
string width = "100";
string height = "100";
bool smartcropping = true;
using (var httpClient = new HttpClient())
{
//setup HttpClient
httpClient.BaseAddress = new Uri(_apiUrlBase);
httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", _apiKey);
//setup data object
HttpContent content = new StreamContent(original);
content.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/octet-stream");
// Request parameters
var uri = $"{_apiUrlBase}?width={width}&height={height}&smartCropping={smartcropping}";
//make request
var response = httpClient.PostAsync(uri, content).Result;
//log result
log.Verbose($"Response: IsSucess={response.IsSuccessStatusCode}, Status={response.ReasonPhrase}");
//read response and write to output stream
thumb = new MemoryStream(response.Content.ReadAsByteArrayAsync().Result);
}
}
My function json is as follows
{
"bindings": [
{
"path": "originals/{name}",
"connection": "thumbnailgenstorage_STORAGE",
"name": "original",
"type": "blobTrigger",
"direction": "in"
},
{
"path": "thumbs/%rand-guid%",
"connection": "thumbnailgenstorage_STORAGE",
"type": "blob",
"name": "thumb",
"direction": "out"
}
],
"disabled": false
}
My Azure storage account is called 'thumbnailgenstorage' and it has two containers named 'originals' and 'thumbs'. The storage account key is KGdcO+hjvARQvSwd2rfmdc+rrAsK0tA5xpE4RVNmXZgExCE+Cyk4q0nSiulDwvRHrSAkYjyjVezwdaeLCIb53g==.
I'm perfectly happy for people to use my keys to help me figure this out! :)
I got this working now. I was writing the output stream incorrectly.
This solution is an Azure function which triggers on the arrival of a blob in an Azure Blob Storage container called 'Originals', then uses the Computer Vision API to smartly resize the image and store in in a different blob container called 'Thumbs'.
Here is the working CSX (c# script):
using System;
using System.Text;
using System.Net.Http;
using System.Net.Http.Headers;
public static void Run(Stream original, Stream thumb, TraceWriter log)
{
int width = 320;
int height = 320;
bool smartCropping = true;
string _apiKey = "PutYourComputerVisionApiKeyHere";
string _apiUrlBase = "https://api.projectoxford.ai/vision/v1.0/generateThumbnail";
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(_apiUrlBase);
httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", _apiKey);
using (HttpContent content = new StreamContent(original))
{
//get response
content.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/octet-stream");
var uri = $"{_apiUrlBase}?width={width}&height={height}&smartCropping={smartCropping.ToString()}";
var response = httpClient.PostAsync(uri, content).Result;
var responseBytes = response.Content.ReadAsByteArrayAsync().Result;
//write to output thumb
thumb.Write(responseBytes, 0, responseBytes.Length);
}
}
}
Here is the integration JSON
{
"bindings": [
{
"path": "originals/{name}",
"connection": "thumbnailgenstorage_STORAGE",
"name": "original",
"type": "blobTrigger",
"direction": "in"
},
{
"path": "thumbs/{name}",
"connection": "thumbnailgenstorage_STORAGE",
"name": "thumb",
"type": "blob",
"direction": "out"
}
],
"disabled": false
}

Resources