Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JWE for P-256 #225

Open
bshambaugh opened this issue Apr 7, 2022 · 16 comments
Open

JWE for P-256 #225

bshambaugh opened this issue Apr 7, 2022 · 16 comments
Labels
enhancement New feature or request pinned a known issue or feature that should not be closed by bots

Comments

@bshambaugh
Copy link
Contributor

bshambaugh commented Apr 7, 2022

I see https://w3c-ccg.github.io/lds-jws2020/ .

This suite support cryptographic agility, see [RFC7696]. This table maps a key type to a subset of [IANA_JOSE] supported signing and encryption algorithms.
kty crvOrSize signature keyAgreement encryption
OKP Ed25519 EdDSA ECDH-ES+A256KW
OKP X25519 ECDH ECDH-ES+A256KW
EC secp256k1 ES256K ECDH ECDH-ES+A256KW
EC P-256 ES256 ECDH ECDH-ES+A256KW
EC P-384 ES384 ECDH ECDH-ES+A256KW
RSA 2048 PS256 RSA-OAEP

I would like the stuff in bold because I would like JWE with P-256?

Here is some babbling from earlier:
[edit most of this thread is blabbering]

This is kind of strange looking:

const kek = concatKDF(sharedSecret, keyLen, alg)
const res = xc20pEncrypter(kek)(cek)

function xc20pEncrypter(key: Uint8Array): (cleartext: Uint8Array, aad?: Uint8Array) => EncryptionResult {
const cipher = new XChaCha20Poly1305(key)
return (cleartext: Uint8Array, aad?: Uint8Array) => {
const iv = randomBytes(cipher.nonceLength)
const sealed = cipher.seal(iv, cleartext, aad)
return {
ciphertext: sealed.subarray(0, sealed.length - cipher.tagLength),
tag: sealed.subarray(sealed.length - cipher.tagLength),
iv,
}
}
}

source: https://github.com/decentralized-identity/did-jwt/blob/master/src/xc20pEncryption.ts#L126-L204

[bshambaugh]
Maybe I should ask with an issue what is needed.I'm trying to reverse engineer the code.https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6I need instead:

| ECDH-ES+A256KW | ECDH-ES using Concat KDF and CEK wrapped with |
| | "A256KW"

[bshambaugh]
I can get A256KW here: https://github.com/StableLib/stablelib/blob/master/packages/aes-kw/aes-kw.test.ts
aes-kw.test.ts

// Copyright (C) 2020 Tobias Looker
// MIT License. See LICENSE file for details.

import { AESKW } from "./aes-kw";
import { encode, decode } from "@stablelib/hex";

https://github.com/[StableLib/stablelib](https://github.com/StableLib/stablelib)|StableLib/stablelibStableLib/stablelib

[bshambaugh]

I have been eying: https://github.com/panva/jose/tree/main/src/jwe
[bshambaugh]

And maybe the jose npm library is the way to go. I still need to fiddle with my JWK, PEM, or DER representation.

[bshambaugh]

the results of that: https://gist.github.com/bshambaugh/4014f8a11025b42774b75f2bbd3f9be7 (edited)

[bshambaugh]

going through the xc20pEncrypter code is manual mode.
[bshambaugh]

I'm trying to match this interface with my function: https://github.com/decentralized-identity/did-jwt/blob/master/src/JWE.ts#L55-L60 (edited)
JWE.ts

export interface Encrypter {
alg: string
enc: string
encrypt: (cleartext: Uint8Array, protectedHeader: ProtectedHeader, aad?: Uint8Array) => Promise
encryptCek?: (cek: Uint8Array) => Promise

https://github.com/[decentralized-identity/did-jwt](https://github.com/decentralized-identity/did-jwt)|decentralized-identity/did-jwtdecentralized-identity/did-jwt

[bshambaugh]

It's not really clear how the JOSE library is going to give me that easily. I think if I can match the interface it will work with the rest of the library.
[bshambaugh]

well hmm....here's an idea of how the JOSE library looks:https://github.com/panva/jose/blob/main/docs/functions/jwe_flattened_decrypt.flattenedDecrypt.md#readme

[bshambaugh]

const jwe = {
ciphertext: '9EzjFISUyoG-ifC2mSihfP0DPC80yeyrxhTzKt1C_VJBkxeBG0MI4Te61Pk45RAGubUvBpU9jm4',
iv: '8Fy7A_IuoX5VXG9s',
tag: 'W76IYV6arGRuDSaSyWrQNg',
encrypted_key: 'Z6eD4UK_yFb5ZoKvKkGAdqywEG_m0e4IYo0x8Vf30LAMJcsc-_zSgIeiF82teZyYi2YYduHKoqImk7MRnoPZOlEs0Q5BNK1OgBmSOhCE8DFyqh9Zh48TCTP6lmBQ52naqoUJFMtHzu-0LwZH26hxos0GP3Dt19O379MJB837TdKKa87skq0zHaVLAquRHOBF77GI54Bc7O49d8aOrSu1VEFGMThlW2caspPRiTSePDMDPq7_WGk50izRhB3Asl9wmP9wEeaTrkJKRnQj5ips1SAZ1hDBsqEQKKukxP1HtdcopHV5_qgwU8Hjm5EwSLMluMQuiE6hwlkXGOujZLVizA',
aad: 'VGhlIEZlbGxvd3NoaXAgb2YgdGhlIFJpbmc',
protected: 'eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0'
}

const {
plaintext,
protectedHeader,
additionalAuthenticatedData
} = await jose.flattenedDecrypt(jwe, privateKey)

console.log(protectedHeader)
const decoder = new TextDecoder()
console.log(decoder.decode(plaintext))
console.log(decoder.decode(additionalAuthenticatedData))

[bshambaugh]

compare to: https://github.com/decentralized-identity/did-jwt/blob/master/src/JWE.ts#L37-L44

[bshambaugh]

export interface JWE {
protected: string
iv: string
ciphertext: string
tag: string
aad?: string
recipients?: Recipient[]
}

[bshambaugh]

they should ultimately both follow a standard.

[bshambaugh]

"https://www.rfc-editor.org/rfc/rfc7516 , https://www.rfc-editor.org/rfc/rfc7518

@bshambaugh bshambaugh added the enhancement New feature or request label Apr 7, 2022
@bshambaugh
Copy link
Contributor Author

bshambaugh commented Apr 7, 2022

This ramble thread may not make sense to many. I'm trying to go through the https://github.com/decentralized-identity/did-jwt/blob/master/src/xc20pEncryption.ts code and seeing how I can map over to JWE for P-256.

@bshambaugh
Copy link
Contributor Author

psudocode for what I think a code chunk might look like:
const kek = concatKDF(sharedSecret, keyLen, alg)
const res = aeskw(kek)(cek) /// maybe this is what "using Concat KDF and CEK wrapped with |
| | "A256KW"" looks like

something says, maybe I don't need to do this I know @ oed always wants things to be light weight though ... so fitting the output to the existing interfaces (if even possible) to the npm package JOSE may not be the way to go... ??

@bshambaugh
Copy link
Contributor Author

I feel like I have to match all (or most) of these interfaces for JWE encryption/decryption with P-256: https://github.com/decentralized-identity/did-jwt/blob/master/src/JWE.ts#L11-L66

@bshambaugh
Copy link
Contributor Author

Aside from RFC7516 and RFC7518 this is the best documentation I have found for JWE: https://www.youtube.com/watch?v=0r-ZDqpYYYI (JSON Web Encryption[JWE] - JWT_3 -- 100bytes.com)

@bshambaugh
Copy link
Contributor Author

bshambaugh commented Apr 7, 2022

@bshambaugh
Copy link
Contributor Author

Here are some of my notes
ed25519Encryption

I realize I have not read every word in RFC 7516 and RFC 7518. I will do this tomorrow. Like a drunkard walking home, I gradually gather information and understanding.

@bshambaugh
Copy link
Contributor Author

I believe xc20encrpyter should be replaced with a function using aes-gcm instead or possibly kept as xc20encrypter (chacha20-poly1305). it corresponds to the "ec" parameter. a256kw corresponds to the "alg" parameter. Thankfully, I also have David Wong's book. I need to spend time perusing this material rather than skipping around.

@bshambaugh
Copy link
Contributor Author

compare content encryption algorithm "aes-gcm" with key wrapping "a256kw"

@mirceanis
Copy link
Member

In the current codebase, there were some assumptions made that don't hold well against the present day.
The names of the encrypters/decrypters are not sufficient to describe what they do.

The algorithm for content encryption key wrapping is ECDH-ES+XC20PKW which means this:

  • the recipient public keys (which are X25519) are used to perform Elliptic Curve Diffie Hellman against a Ephemeral Static keypairs (ECDH-ES) (they are named epk in the code you printed)
  • for each recipient, this results in a "shared secret" that is then used in concatKDF to generate a key encryption key (kek)
  • The kek for each recipient is then used to encrypt(wrap) the content encryption key (cek) using XChaha20Poly1305 key wrapping (XC20PKW)

If I'm not mistaken, the content is then encrypted using XChaha20Poly1305 too, but using the content key cek


To add P-256 ECDH-ES+A256PKW you would be changing the algorithm from above to use P-256 recipient and ephemeral keys to perform the ECDH-ES (instead of the X25519 keys we already use), and then using AES256KW to wrap the content encryption key.

Then, I believe the content is encrypted using A256GCM.


The P-256 functionality can be imported from elliptic and I believe the AES256KW and A256GCM are exported by stablelib, so there should be no new library imports.
@oed should be as happy about this as I am :)

If you want to start implementing this, please do so with minimal changes to the rest of the codebase.
If such changes are necessary, please flag them and we can create separate small PRs for them.
This is preferable to creating one BIG PR.

Have fun!

@stale
Copy link

stale bot commented Jun 17, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Jun 17, 2022
@bshambaugh
Copy link
Contributor Author

@mirceanis , I have a big checklist of what I need to do, and this doesn't hit the top. Thanks for outlining this. I'll keep it in mind for another time around. Should I close this issue or stash it somewhere so I don't forget?

@stale stale bot removed the stale label Jun 23, 2022
@mirceanis
Copy link
Member

I'll leave this open and pin it so that it doesn't get automatically staled.
It seems like we have the tools in place to add this required algorithm (P-256 ECDH-ES+A256PKW). What we lack is bandwidth :)
Perhaps if we rephrase it better or split it into smaller tasks some other folks would like to tackle this.

If you plan to work on this or a connected issue please say so, so that we don't overlap by mistake.

@mirceanis mirceanis added the pinned a known issue or feature that should not be closed by bots label Jun 23, 2022
@bshambaugh
Copy link
Contributor Author

bshambaugh commented Dec 14, 2022

_The following is a file on my computer called description_of_did_jwt.txt in /home/ubuntu/Downloads (last modified 8/31/2022). I am posting it here to grease the skids in my brain and for easy reference:


'ECDH-1PU+XC20PKW'

ECDH-ES+A256KW

DID you know that I've been working to amend the did-jwt library to include support for the secp256r1 curve? Here are some interpretations of the library I chose to JOT down:

  • folder signers ts files : signs an arbitrary data payload with a private key, both formatted as Uint8Arrays . returns a string of some sort.

  • folder blockchains ts files: coverts public keys to Bitcoin. Cosmos, and Ethereum URIs following bip122, cosmos (bech32 encoding of ripemd hash of sha256 hashed compressed public key), and eip155.

  • Digest.ts: exports sha256 hasher, keccak 256 hasher, public key to Ethereum address creator, concatKDF function that produces a key encryption key for ECDH-1PU-XC20KW (although the function is generic. ECDH-ES-A256KW is mentioned in linked documentation and exploration to
    modify the library to do this is here: JWE for P-256 #225)

  • ECDH.ts: Described as: "A wrapper around mySecretKey that can compute a shared secret using theirPublicKey. The promise should resolve to a Uint8Array containing the raw shared secret." "Wraps an X25519 secret key into an ECDH method that can be used to compute a shared secret with a public key."

  • JWE.ts: Functions for Json Web Encryption, Decrytion, and Validation in the abstract. Representation as JSON object apparently following https://datatracker.ietf.org/doc/html/rfc7516#section-7.2.1 .

  • JWT.ts: (utilizes Signer and Verifier Alg)

    • decodeJWT returns a JSON object representing the payload
    • createJWS takes a JSON object representing a payload, a signer of type Signer, a header JSON string that may contain typ: 'JWT' and alg properties plus and additional key-value pairs, plus optional canonicalization to string. If no alg is specified the default alg "ES256K"
      is used. Returns a JSON Web Signature. Utilizes SignerAlgorithm.ts
    • createJWT takes a payload, JWTOptions JSON object containing issuer, signer, and optional expiresIn, alg, and canonicalize options, and a header JSON string that may contain typ: 'JWT' and alg properties plus and additional key-value pairs, plus optional canonicalization to string. If no alg is specified the default alg "ES256K"
      is used. Returns a JSON Web Signature. Utilizes createJWS.
    • verifyJWS "Verifies given JWS. If the JWS is valid, returns the public key that was used to sign the JWS, or throws an Error if none of the pubKeys match." Uses function verifyJWSDecoded that uses VerifierAlgorithm.ts . Checks to see that the JWS has the syntactic form header.data.signature . The verificationMethod interface is followed where id,type, and controller string properties are a MUST, a publicKey in the specified formats is optional, and a blockchainAccountId and ethereumAddress are also optional. However if a public key not specified either a blockChainAccountId or ethereumAddress should be specified. If none of these are specified, it's no bueno.
    • verifyJWT takes a JWT string and a number of verification options through the JWTVerifyOptions interface. From the resolver property-value pair specified in the JWTVerifyOptions interface MUST be specified. The JWT string MUST contain a value for the iss property. If it doesn't, the JWT has no did and an error is thrown. The iss property can match two const values specific to the library for self-issuance. For the JWTOptions, allow for either the auth or proofPurpose properties to be used, but prefer proofPurpose. What happens if both auth and proofPurpose are both undefined? Check this. The function works without error. This function, verifyJWT, depends on resolveAuthenticator. resolveAuthenticator verifies that the authenticator is in the proper form from a resolveable did document. The authenticator is used for verifying the json web signature. If the signer for the jwt is valid, investigate properties in the payload. If these properties do not match, throw an error that there is not a verification Method that matches the signature.
    • resolveAuthenticator: This is used by verifyJWT. Note that this does not do fancy math to recover the public key from the signature and a message for the case of ECDSA, but instead looks in the DID document to find the verificatioMethod which may point to the public key. Check that the JWT is valid for the spefic time with a small offset and the aud or audience is specified and it maches the did or callbackUrl. resolveAuthenticator returns an object matching the didAuthenticator interface which could contain a publicKey from a did document if it exists from a specified issuer did if it exists using interface VerificationMethod from interface DIDResolutionResult, otherwise it returns an error message using the interface DIDResolutionMetadata.
      For the verificationMethod interface, return non-null property-value pairs. For a legacy did document return from the publicKey property.Grab the verificationMethod id when a ProofPurposeTypes is matched, else ruturn null. Throw an error if a supported public key type matching the alg parameter is not in a did document.
  • SignerAlgorithm.ts : returns a function SignerAlg that takes alg definition according to RFC7518 or an alg definition with a recovery parameter that checks for a recovery parameter and returns a base64url formatted string with a recovery bit at the end of the signature if a recovery parameter specified (for the correct alg when the promise in the function is resolved), else returns a signature (for the correct alg when the promise is resolved). Function returned uses Signers type definition utilized in folder signers.

  • VerifierAlgorithm.ts : (checks valid signature?? and returns??) returns a function VerifierAlgorithm that matched the interface VerificationMethod that takes an alg definition according to RFC7518 or an alg definition with a recovery parameter which in turn takes data, a signature, and authenticators defined by a VerificationMethod interface that requires an id, type, and controller, and optionally includes a public key encoded as base58, Base64, Hex, as a JsonWebKey, or with Multibase, in addition to a blockChainAccountId and EthereumAddress. If blockChainAccountId and EthereumAddress are specified and there is no public key specified trigger a recovery function generate a public key from the data and signature and covert this to an Ethereum address and return invalid signature if the EthereumAddress is not matched else return the function VerifierAlgorithm. VerifierAlgorithm returns the publicKey in interface VerificationMethod corresponding to the signature and data if exists, error if otherwise.

  • xc20Encryption.ts : (JWE stuff that utilizes JWE.ts)

  • util.ts: (utils used elsewhere) Convert bytes to and from base64url, base64, base58, hex. Convert UTF8 string to bytes. Add left padding zeros to a string. Convert a JSON blob in the form { r, s, v } where r is the r part of the signature, s is the s part of the signature, and v is the recovery flag bit.
    to and from base64url. Concatenate a base64 representation of ciphertext and a tag and convert to bytes.

Internal Interdependencies:

  • folder signers includes + EdDSASigner.ts which utilizes { Signer } type from JWT.ts and { bytesToBase64url, stringToBytes } functions from util.ts,
    + EllipticSigner.ts which utilizes { Signer } type from JWT.ts and ES256KSigner function from ES256KSigner.ts ,
    + ES256KSigner.ts which utilizes { leftpad, toJose } functions from util.ts and { Signer } type from JWT.ts,
    + ES256Signer.ts which utilizes { leftpad, toJose } functions from util.ts and { Signer } type from JWT.ts, , [ actually, I have to revert this PR to match form in EdDSASigner.ts since it is standard to use secp256k1 not secp256r1 in Ethereum address creation. Therefore, likely no recovery of secp256r1 public keys universally from Ethereum Address].
    + NaclSigner.ts which utilizes { EdDSASigner } function from EdDSASigner.ts and { Signer } type from JWT.ts,
    + and SimpleSigner.ts which utilizes { fromJose, hexToBytes } functions from util.ts and ES256KSigner function from ES256KSigner.ts .

  • folder blockchains includes + bip122.ts which utilizes { bytesToBase58, base58ToBytes } functions from util.ts and { sha256 } function from Digest.ts and { Ripemd160 } from ripemd160.ts,
    + cosmos.ts which utilizes { sha256 } function from Digest.ts and { Ripemd160 } from ripemd160.ts,
    + index.ts which utilizes { publicKeyToAddress as bip122 } const function from bip122.ts and { publicKeyToAddress as cosmos } const function from comsmos.ts and { toEthereumAddress } function from Digest.ts,
    + and folder utils which contains ripemd160.ts (copy of: https://github.com/crypto-browserify/ripemd160/blob/master/index.js) .

  • JWE,ts utilizes { base64ToBytes, bytesToBase64url, decodeBase64url, toSealed } functions from util.ts

  • JWT.ts utilizes SignerAlg function from SignerAlgorithm.ts and VerifierAlgorithm from VerifierAlgorithm.ts

  • SignerAlgorithm.ts utilizes { Signer, SignerAlgorithm } types from JWT.ts, { EcdsaSignature } interface and {fromJose, toJose } function from util.ts

  • VerifierAlgorithm.ts utilizes { sha256, toEthereumAddress } functions from Digest.ts, { hexToBytes, base58ToBytes, base64ToBytes, bytesToHex, stringToBytes } functions and { EcdsaSignature } interface from util.ts and { verifyBlockchainAccountId } function in index.ts from 'blockchains' folder

  • xc20Encryption.ts utilizes { concatKDF } function from Digest.ts, { bytesToBase64url, base58ToBytes, encodeBase64url, toSealed, base64ToBytes } functions from util.ts, { Recipient, EncryptionResult, Encrypter, Decrypter } interfaces and {ProtectedHeader } type
    from JWE.ts, and { ECDH } type function that returns a Promise as Uint8Array from ECDH.ts


  • decodeJWT returns a JSON object representing the payload
  • createJWS takes a JSON object representing a payload, a signer of type Signer, a header JSON string that may contain typ: 'JWT' and alg properties plus and additional property-value pairs, plus optional canonicalization to string. If no alg is specified the default alg "ES256K" is used. Returns a JSON Web Signature. Utilizes SignerAlgorithm.ts
  • createJWT takes a payload, JWTOptions JSON object containing issuer, signer, and optional expiresIn, alg, and canonicalize options, and a header JSON string that may contain typ: 'JWT' and alg properties plus and additional property-value pairs, plus optional canonicalization to string. If no alg is specified the default alg "ES256K"
    is used. Returns a JSON Web Signature. Utilizes createJWS.
  • verifyJWS "Verifies given JWS. If the JWS is valid, returns the public key that was used to sign the JWS, or throws an Error if none of the pubKeys match." Uses function verifyJWSDecoded that uses VerifierAlgorithm.ts . Checks to see that the JWS has the syntactic form header.data.signature . The verificationMethod interface is followed where id,type, and controller string properties are a MUST, a publicKey in the specified formats is optional, and a blockchainAccountId and ethereumAddress are also optional. However if a public key not specified either a blockChainAccountId or ethereumAddress should be specified. If none of these are specified, it's no bueno.
  • verifyJWT takes a JWT string and a number of verification options through the JWTVerifyOptions interface. From the resolver property-value pair specified in the JWTVerifyOptions interface MUST be specified. The JWT string MUST contain a value for the iss property. If it doesn't, the JWT has no did and an error is thrown. The iss property can match two const values specific to the library for self-issuance. For the JWTOptions, allow for either the auth or proofPurpose properties to be used, but prefer proofPurpose. What happens if both auth and proofPurpose are both undefined? Check this. The function works without error. This function, verifyJWT, depends on resolveAuthenticator. resolveAuthenticator verifies that the authenticator is in the proper form from a resolveable did document. The authenticator is used for verifying the json web signature. If the signer for the jwt is valid, investigate properties in the payload. If these properties do not match, throw an error that there is not a verification Method that matches the signature.
  • resolveAuthenticator: This is used by verifyJWT. Note that this does not do fancy math to recover the public key from the signature and a message for the case of ECDSA, but instead looks in the DID document to find the verificatioMethod which may point to the public key. Check that the JWT is valid for the spefic time with a small offset and the aud or audience is specified and it maches the did or callbackUrl. resolveAuthenticator returns an object matching the didAuthenticator interface which could contain a publicKey from a did document if it exists from a specified issuer did if it exists using interface VerificationMethod from interface DIDResolutionResult, otherwise it returns an error message using the interface DIDResolutionMetadata.
    For the verificationMethod interface, return non-null property-value pairs. For a legacy did document return from the publicKey property. Grab the verificationMethod id when a ProofPurposeTypes is matched, else ruturn null.
    Throw an error if a supported public key type matching the alg parameter is not in a did document.

/// do this same decomposition with ts-ucan to see if you can derive a a verifiable cred from did-jwt (from did-jwt-vc) ... ts-ucan not on computer (need to download)

@bshambaugh
Copy link
Contributor Author

bshambaugh commented Dec 14, 2022

The above writing must have been written between PR #240 and
#246

@TallTed
Copy link

TallTed commented Feb 7, 2023

@bshambaugh @mirceanis — If you intend for this thread (which I came to via W3C CCG 2/7/23 - Multi-Signature Verifiable Credentials and Conditional Proofs — AgendaRaw transcript, Raw audio, Raw video) to be digested by others, it will be valuable to cycle through your previous comments and insert markdown tags to format things more clearly, at least and especially for the code-chunks where indentation can help a lot with comprehensibility, and to strip out the repetitive "talking to yourself" [bshambaugh] lines in the initial comment, even if it is "mostly blabbering".

@bshambaugh
Copy link
Contributor Author

bshambaugh commented Feb 7, 2023

@TallTed , I appreciate the comment. I will need to peruse and refine. It is typical of me to have a quite messy thread and then speak with more succinctness as I gain understanding of a previously unfamiliar subject. This thread was useful to me because it allowed me to organize my developing comprehension. I linked to this Issue to provide context of my intentions to Jack, but I am leaning toward the prediction of this not interfering with his efforts. I think your suggestion of greater organization could help accomplish the task of implementing JWE for P256. Perhaps, I could write something more succinct and then point to this rougher draft.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request pinned a known issue or feature that should not be closed by bots
Projects
None yet
Development

No branches or pull requests

3 participants