decentralized-identity / did-jwt

Create and verify DID verifiable JWT's in Javascript
Apache License 2.0
336 stars 71 forks source link

setting up a remote signer #226

Closed bshambaugh closed 2 years ago

bshambaugh commented 2 years ago

Is there are proper way to set up a remote signer?

Here is a gist where I have been experimenting: https://gist.github.com/bshambaugh/ef2aee85fa5ae0091b3433ba26c6234b

I asked the following question in context of the gist. Is there a way to substitute signerTwo in place of signer in createJWT?

bshambaugh commented 2 years ago

I'm waffling on asking. I am opening this issue again to try to gain clarity. Maybe I just don't understand JavaScript well enough.

bshambaugh commented 2 years ago

`const didJWT = require('did-jwt') var u8a = require('uint8arrays'); var EC = require('elliptic').ec;

var ec = new EC('p256');

const value = 'howdy';

function bytesToBase64url(b) { return u8a.toString(b, 'base64url') }

async function callToHSM(value) {

const privateKey = '0x040f1dbf0a2ca86875447a7c010b0fc6d39d76859c458fbe8f2bf775a40ad74a'
const keypairTemp = ec.keyFromPrivate(privateKey)
const buffferMsg = Buffer.from(value)
let hexSig = keypairTemp.sign(buffferMsg)
const xOctet = u8a.fromString(hexSig.r.toString(),'base10');
const yOctet = u8a.fromString(hexSig.s.toString(),'base10');
const hexR = u8a.toString(xOctet,'base16');
const hexS = u8a.toString(yOctet,'base16');
return u8a.fromString(hexR+hexS,'hex');

}

async function JsonWebToken(value) { const signatureBytes = await callToHSM(value) return bytesToBase64url(signatureBytes) }

let signerFour = async function() { await JsonWebToken(value) }

async function JsonWebTokenT() { let jwt = await didJWT.createJWT( { aud: 'did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74', exp: 1957463421, name: 'uPort Developer' }, { issuer: 'did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74', signerFour }, { alg: 'ES256K' } /// more correct to say alg: 'ES256' , but not supported yet ) console.log(jwt) }

JsonWebTokenT()`

bshambaugh commented 2 years ago

signerFour could be rewritten in some other way. let signerFour = async function() { await JsonWebToken(value) }

mirceanis commented 2 years ago

@bshambaugh I'm a bit confused. What are you trying to achieve?

bshambaugh commented 2 years ago

A lot of my code is like: https://github.com/decentralized-identity/did-jwt/blob/master/README.md#1-create-a-did-jwt , except with my own modifications (everything that goes into signerFour) are in place of signer)

bshambaugh commented 2 years ago

Hang on, I'm making a diagram describing my setup.

bshambaugh commented 2 years ago

I am trying to develop a diagram explaining that the private key exists on a device (on the cryptographic co-processor connected by wire the the MCU) that is not the same one as where the javascript is running (On the Node.js server). I believe this is unlike what is normally expected for this library. Normally the private key comes internally by calling elliptic. (e.g.https://github.com/decentralized-identity/did-jwt/blob/master/src/signers/ES256KSigner.ts . ) The closest thing that I could find to this was: https://github.com/decentralized-identity/did-jwt/blob/master/docs/guides/index.md#creating-custom-signers-for-integrating-with-hsm .

diag

bshambaugh commented 2 years ago

Code for Running on Node.js server for Ceramic with key-did-provider-ed25519 (pulled from https://github.com/bshambaugh/BlinkyProject/tree/master/CubeCellandHeltecESP32_try7/esp8266_shop_websockets/CeramicToDo I think):

import KeyResolver from 'key-did-resolver'
import {Ed25519Provider} from 'key-did-provider-ed25519'  /// replace with something for P-256, P-384, P-521 .... etc
import {randomBytes} from '@stablelib/random'  /// not needed unless used for a nonce or something
// include did-jwt for verifiable cred or other library for nft (ceramic network makes assertions about did:key string)

// this stuff might be a bit beyond the scope
import { CeramicClient } from '@ceramicnetwork/http-client'
const API_URL = "https://ceramic-clay.3boxlabs.com"
const ceramic = new CeramicClient(API_URL)
//

const seed = randomBytes(32) // 32 bytes with high entropy   /// this line makes no sense for a remote signer
const provider = new Ed25519Provider(seed)   /// replace for provider for P-256, P-384, P-521 etc ... (signer is remote)

const did = new DID({ provider, resolver: KeyResolver.getResolver() })
const auth = await did.authenticate()
console.log('auth is')
console.log(auth) // this spits out the did:key

// see https://github.com/ceramicstudio/idx-assignment and https://github.com/ceramicstudio/js-glaze to create more documents

// create JWS ... specified in eip-2844 interface
const { jws, linkedBlock } = await did.createDagJWS({ hello: 'world' })

// create JWE ... specified in eip-2844 interface
const jwe = await did.createDagJWE({ very: 'secret' }, [did.id])

console.log('jwe is:');
console.log(jwe);

// decrypt JWE  ... ... specified in eip-2844 interface
const decrypted = await did.decryptDagJWE(jwe)

console.log('decrypted is:');
console.log(decrypted);

// this stuff may be a bit beyold the scope
ceramic.did = did
ceramic.did.setProvider(provider)

// Contributor Author @bshambaugh bshambaugh 10 days ago `

https://github.com/ceramicnetwork/key-did-provider-ed25519 https://github.com/ceramicnetwork/js-ceramic

bshambaugh commented 2 years ago

Code needed for key-did-provider-p256

Here is some scratch code with comments for key-did-provider-p256 that does not work yet: https://github.com/bshambaugh/key-did-provider-p256/blob/master/src/index.ts

https://github.com/bshambaugh/key-did-provider-p256/blob/master/src/index.ts#L40-L45

  const kid = `${did}#${did.split(':')[2]}`
  // need remote signer here as well: https://github.com/decentralized-identity/did-jwt/blob/cebf2e6f255e559a1275bb97b35146ce72ce27f5/docs/guides/index.md#creating-custom-signers-for-integrating-with-hsm
  const signer = ES256Signer(u8a.toString(secretKey, B64)) // look at did-jwt tests to find what ES256Signer requires
  const header = toStableObject(Object.assign(protectedHeader, { kid, alg: 'EdDSA' }))
  return createJWS(typeof payload === 'string' ? payload : toStableObject(payload), signer, header)

https://github.com/bshambaugh/key-did-provider-p256/blob/master/src/index.ts#L62-L67

    did_createJWS: async ({ did, secretKey }, params: CreateJWSParams & { did: string }) => {
    const requestDid = params.did.split('#')[0]
    if (requestDid !== did) throw new RPCError(4100, `Unknown DID: ${did}`)
    const jws = await sign(params.payload, did, secretKey, params.protected)
    return { jws: toGeneralJWS(jws) }
  },
mirceanis commented 2 years ago

It's confusing to think of all the layers at once. Try to eliminate ceramic and anything else that is not absolutely essential from the picture at first.

The remote signer gets a bunch of data to be signed and return a signature. That's it. And that is what const signatureBytes = await call.to.your.HSM.backend(data) means in the instructions for creating remote signers

As long as you can send some data to your HSM/arduino/etc and get some other data back, you have the premise to create a remote signer.

The private key will be stored on your arduino, and it is there that you will have to implement the signing logic, but that is way outside the scope of this library.

I hope this clarifies some things for you. Good luck with your implementation!

bshambaugh commented 2 years ago

Okay, it turns out I was having trouble seeing the forest through the trees. This link isn't how I discovered the error in my thinking, but it does illustrate what should be in createJWT: https://github.com/decentralized-identity/did-jwt/blob/master/docs/guides/index.md#parameters

Here is the code that I have settled on:

const didJWT = require('did-jwt')
var u8a = require('uint8arrays');
var EC = require('elliptic').ec;

var ec = new EC('p256');

const value = 'howdy';

function bytesToBase64url(b: Uint8Array) {
  return u8a.toString(b, 'base64url')
}

async function callToHSM(value: string) {

    const privateKey = '0x040f1dbf0a2ca86875447a7c010b0fc6d39d76859c458fbe8f2bf775a40ad74a'
    const keypairTemp = ec.keyFromPrivate(privateKey)
    const buffferMsg = Buffer.from(value)
    let hexSig = keypairTemp.sign(buffferMsg)
    const xOctet = u8a.fromString(hexSig.r.toString(),'base10');
    const yOctet = u8a.fromString(hexSig.s.toString(),'base10');
    const hexR = u8a.toString(xOctet,'base16');
    const hexS = u8a.toString(yOctet,'base16');
    return u8a.fromString(hexR+hexS,'hex');

}

async function JsonWebToken(value: string) {
 const signatureBytes = await callToHSM(value)
 return bytesToBase64url(signatureBytes)
}

/*
let signerFour = async () =>  { await JsonWebToken(value) }

console.log(signerFour);
*/

let signer = async () =>  { await JsonWebToken(value) }

/*
const signer = didJWT.ES256KSigner(didJWT.hexToBytes('278a5de700e29faae8e40e366ec5012b5ec63d36ec77e8a2417154cc1d25383f'))

console.log(signer)
*/

async function JsonWebTokenT() {
  let jwt = await didJWT.createJWT(
     { aud: 'did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74', exp: 1957463421, name: 'uPort Developer' },
     { issuer: 'did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74', signer }//,
    // { alg: 'ES256' }
  )
  console.log(jwt)
}

JsonWebTokenT()

Of course the code for callToHSM will be different. Most of the logic as you said will be on the device.