panva / paseto

PASETO (Platform-Agnostic SEcurity TOkens) for Node.js with no dependencies
MIT License
428 stars 26 forks source link

add v3.local, v3.public, and v4.public #19

Closed panva closed 3 years ago

panva commented 3 years ago

See https://github.com/paragonie/paseto/pull/127

This PR will be updated as the draft evolves.

Similar to v2.local - the new v4.local will not be implemented because it is not possible to do so without requiring libsodium. This will be revisited as XChaCha20-Poly1305 and XChaCha20 lands in OpenSSL 3.x and it being bundled with Node.js. Other features may be still be missing then though so at this point it is unlikely to be considered even long-term.

This will be a breaking release (v3.0.0). It will require Node.js >= 16.0.0 due to a number of new crypto APIs that simplify the codebase.

paragonie-security commented 3 years ago

If you need test vectors, I created some in the PHP implementation: https://github.com/paragonie/paseto/pull/128

panva commented 3 years ago

If you need test vectors, I created some in the PHP implementation: paragonie/paseto#128

The json files are missing the input nonces. And while having limited use, signed vector corresponding private keys would be nice, as well as PEM key values where applicable.

NB:

v1

"pem-public-key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyaTgTt53ph3p5GHgwoGW\nwz5hRfWXSQA08NCOwe0FEgALWos9GCjNFCd723nCHxBtN1qd74MSh/uN88JPIbwx\nKheDp4kxo4YMN5trPaF0e9G6Bj1N02HnanxFLW+gmLbgYO/SZYfWF/M8yLBcu5Y1\nOt0ZxDDDXS9wIQTtBE0ne3YbxgZJAZTU5XqyQ1DxdzYyC5lF6yBaR5UQtCYTnXAA\npVRuUI2Sd6L1E2vl9bSBumZ5IpNxkRnAwIMjeTJB/0AIELh0mE5vwdihOCbdV6al\nUyhKC1+1w/FW6HWcp/JG1kKC8DPIidZ78Bbqv9YFzkAbNni5eSBOsXVBKG78Zsc8\nowIDAQAB\n-----END PUBLIC KEY-----",
"pem-private-key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJpOBO3nemHenk\nYeDCgZbDPmFF9ZdJADTw0I7B7QUSAAtaiz0YKM0UJ3vbecIfEG03Wp3vgxKH+43z\nwk8hvDEqF4OniTGjhgw3m2s9oXR70boGPU3TYedqfEUtb6CYtuBg79Jlh9YX8zzI\nsFy7ljU63RnEMMNdL3AhBO0ETSd7dhvGBkkBlNTlerJDUPF3NjILmUXrIFpHlRC0\nJhOdcAClVG5QjZJ3ovUTa+X1tIG6Znkik3GRGcDAgyN5MkH/QAgQuHSYTm/B2KE4\nJt1XpqVTKEoLX7XD8VbodZyn8kbWQoLwM8iJ1nvwFuq/1gXOQBs2eLl5IE6xdUEo\nbvxmxzyjAgMBAAECggEAXbaMsNrfjIp2ezep93u2j4LcPmFHMBwyfoDO9/2pz5XJ\nsQjpGeNMfENlYrkRqNI/j+xDXl7yK9STQmhZ0nnd94v6GdC/CxpvbyCCFKCGvEza\nQbAYDVeA75JVrCom3xKO8T5D7//TVkorQ7IDRwMmNfcv1Gg9Q3+agx4A8XDSGqQU\nSGbvYZJUIRjV5j5w5eYveJzGeyeVQP/9JL2rfdHEXbLmiJbV52pxGqFb/svXJgxv\nEcVRzFaHxZTCOfUACGgI5TMXxkH7Rf88+H7WdiirrHCovRkoPCtDxIOFb+s0Mt47\n2CJzmuXsR61FBFsW+xnjQB2xfbBT6VYVSQHjYCO6gQKBgQDnP8JeoMV9nwxcI8A5\n66LzLkc0O27fRpH4c5ewEOdEjyG5ewY0PTgV+ZdHueSCGvTx8VUOYTg94pSTzXRg\nlv4gAmjzCs5nTrzafrIQw7nyiaoqKf6IhRWs9ctIujkIKGurDYni+DOiXM/Dc3kj\nwUl0uiZv0WStXzgf4i+I8jKXIQKBgQDfOfAAbb1BfoBMR8WkZU5yiCiscKe13ieJ\npYKrmd7mwUv4TAt5VzXMP3KPVrFwN6UgINnEJnKdTuQOetBwWRPRuMf+ALwmTe/t\ndP2XVQMOoLVvuub9fnAUhn+uY1rWtVmEvWj+0rHm28zazInWr3m/s13KAUgQhb6p\nsgptzpbPQwKBgGxRl1AP6rH/ECEQtffrgjZ6lOvIcxSuz60bKBBWup2Ilfl1wOAz\nVNQmR1BXqMuwqM+zhW3o6BlEyue4syyTTZHczyAZDbmiTh/ifLIRnEYZadW6Ofnk\nrNSJhaEZaaGCnXxQKShhrn39D2yz6ChxX2EH2P1Dje8PzRBSOIXjPQNBAoGALtdB\nfVWJuQyKb3dACdcYNwBLSKP7DTaopUGNweRv2YwGHPwYDEY4i7tklp9ibGHAzJUY\nHQjUVB4RzNgIlQqcFg3oKWyODpucFP/PlsnH8nHWoLNfdSHq8uOmNzmx/gvf1PLJ\n7W7Y1dCZk/AHnH0F1ywUKidKr+zgrUsm1RPcoXECgYEA0ZxDyc5uLebjBE7IquQJ\nZxbEUUyenDG/Slbvbsef3C5o6zhRt6wKfCalwxN/MZQO7NhcK0CrakmXrgcbrCx2\nRaaMFMkSmbpv2Js4E3eoVXbNDQfLIqUxbEi5VKP2A6jrWEXtQf1cHpHgdF2WkE64\nhuABZnjp2SP38cz2i90/QjI=\n-----END PRIVATE KEY-----\n",

v2, v4

"pem-public-key": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----",
"pem-private-key": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----",

v3

"pem-public-key": "-----BEGIN PUBLIC KEY-----\nMEYwEAYHKoZIzj0CAQYFK4EEACIDMgAC+8t8ae4cYFeb56M0E0h42cXFvzXVUtq2\nPAFAOX7RTO9jfXcgklxEaZ6jDnKHTHL7\n-----END PUBLIC KEY-----",
"pem-private-key": "????"

Could you let me know the v3 private key used to generate the vectors?

paragonie-security commented 3 years ago

If you want the -pem suffix, it will only be made available in v3 because it doesn't make sense for v2/v4 and v1 is RSA so it's already PEM encoded.

EDIT: https://github.com/paragonie/paseto/pull/128/commits/bdcd113b080811249a1e0d2721632e12aca6934f :)

panva commented 3 years ago

doesn't make sense for v2/v4

There were people for whom the conversion between spki/pkcs8 and the raw bytes format that sodium uses was a problem.

I'd kindly ask that we stop making a big deal out of representing keying material with standard based representations, as such all four versions signed paseto keys CAN be represented with spki public keys as well as pkcs8 private keys. SPKI and PKCS8 even allow for compressed representations for v3 too.

For now all I'm lacking is the v3 private key, keep the vectors in any shape you'd like - I have filled in the blanks myself, aside of v3.public. private key.

The specs already say that each implementation must make sure the points are actually on their respective curves, that ought to be enough to relax the need to only represent keys the "one way" that libsodium uses.

As an aside:

I have an implementation of a conversion function between your "raw" keys and Node's KeyObject (which always makes sure points are on their respective curves). Do you have a suggestion for what these should be called?

paragonie-security commented 3 years ago

There were people for whom the conversion between spki/pkcs8 and the raw bytes format that sodium uses was a problem.

I didn't think an ASN.1 object identifier even existed for Ed25519, but it does, in RFC 8410, so I think I can do that.

For now all I'm lacking is the v3 private key, keep the vectors in any shape you'd like - I have filled in the blanks myself, aside of v3.public. private key.

No, they're in the JSON. secret-key-pem.

paragonie-security commented 3 years ago

https://github.com/paragonie/paseto/pull/128/commits/bdcd113b080811249a1e0d2721632e12aca6934f has the PEM-encoded keys.

panva commented 3 years ago

@paragonie-security the v2/v4 pem private keys in your vector files fail to pass openssl as valid.

the valid value would be

"-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----"
paragonie-security commented 3 years ago

OK, I'll make the changes.

We only ever use v2/v4 with libsodium which doesn't touch ASN.1, so I had no way of testing this. The PEM we provided was from the following snippet:

const { composePrivateKey, composePublicKey } = require('@credify/crypto-key-composer');

function privateToPem(privateKey) {
    var decomposed = {
        format: 'pkcs8-pem',
        keyAlgorithm: { id: 'ed25519' },
        keyData: {
            seed: privateKey,
        },
    }
    return composePrivateKey(decomposed)
}

function publicToPem(publicKey) {
    var decomposed = {
        format: 'spki-pem',
        keyAlgorithm: { id: 'ed25519' },
        keyData: {
            bytes: publicKey,
        },
    }
    return composePublicKey(decomposed)
}

const sk = Buffer.from('b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2', 'hex');
const pk = Buffer.from('1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2', 'hex');

console.log(privateToPem(sk), publicToPem(pk));
panva commented 3 years ago

OK, I'll make the changes.

We only ever use v2/v4 with libsodium which doesn't touch ASN.1, so I had no way of testing this. The PEM we provided was from the following snippet:

const { composePrivateKey, composePublicKey } = require('@credify/crypto-key-composer');

function privateToPem(privateKey) {
    var decomposed = {
        format: 'pkcs8-pem',
        keyAlgorithm: { id: 'ed25519' },
        keyData: {
            seed: privateKey,
        },
    }
    return composePrivateKey(decomposed)
}

function publicToPem(publicKey) {
    var decomposed = {
        format: 'spki-pem',
        keyAlgorithm: { id: 'ed25519' },
        keyData: {
            bytes: publicKey,
        },
    }
    return composePublicKey(decomposed)
}

const sk = Buffer.from('b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2', 'hex');
const pk = Buffer.from('1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2', 'hex');

console.log(privateToPem(sk), publicToPem(pk));

Shouldn't the secret-key used only be the private 32 bytes? It is currently private+public which is what you've sent into that snippet and produced that.

paragonie-security commented 3 years ago

Technically no, but maybe seed should have been bytes. Mea culpa!

panva commented 3 years ago

Technically no, but maybe seed should have been bytes. Mea culpa!

And what's the public part technically good for?

paragonie-security commented 3 years ago

It's an optimization since the public key is used in the Ed25519 signing process. https://duo.com/labs/tech-notes/whats-an-eddsa#section5

This provides a property that ECDSA doesn't provide. https://www.bolet.org/~pornin/2005-acns-pornin+stern.pdf

panva commented 3 years ago

Yeah but as you can see the private pem leads to the same signatures your vectors do without this information since the public key can always be recalculated when needed for signing I assume?

Anyway I'll just grab the first 32 bytes for this libs use

paragonie-security commented 3 years ago

I'm sure that's totally fine. We're coming from the "how libsodium does it" perspective.

panva commented 3 years ago

I've noticed.

paragonie-security commented 3 years ago

You're the first person to ask us for PEM encoding, so whatever's easier for you is what we're going to go with. :)

If it turns out down the line to slow down signing without the public key precomputed, implementors are free to use the full 64 bytes in their secret keys, and this shouldn't require any changes to the test vectors. (Though, I wonder if we should provide a test vector for each key size. Do you have any concerns about that?)

panva commented 3 years ago

Isn't precomputing dangerous the same way uncompressed public ec key is?

Anyway I don't see a way to instantiate an OpenSSL backed KeyObject with both private and public keys, or at least didn't figure out the ASN.1 encoding for it. And the vector outcome is the same for me with raw private as it is for you with the combined key using sodium.

paragonie-security commented 3 years ago

Isn't precomputing dangerous the same way uncompressed public ec key is?

Nope. The uncompressed EC public key attack doesn't work on EdDSA.

Anyway I don't see a way to instantiate an OpenSSL backed KeyObject with both private and public keys, or at least didn't figure out the ASN.1 encoding for it. And the vector outcome is the same for me with raw private as it is for you with the combined key using sodium.

I've been trying and couldn't either. I think we landed on the correct PEM-encoded private key for Ed25519. Thanks for taking the time to chase this answer down.

panva commented 3 years ago

Now that I have an implementation of a conversion function between the sodium keys and Node's KeyObject. Do you have a suggestion for what these should be called when exposed to users? I think these would aid in scenarios where devs need different implementations to interoperate.

E.g. paseto.V2.importSodiumPrivateKey ?

paragonie-security commented 3 years ago

I think that's a good descriptive name. importRawBytes() would also be appropriate, but probably less meaningful.

panva commented 3 years ago

@paragonie-security can you take a look?

NB: that being said these functions are now purely for optimization because one can pass the raw bytes directly to sign/verify now.

paragonie-security commented 3 years ago

These helper functions look good to us.

paragonie-security commented 3 years ago

Sorry, we just realized Github is sending a notification here every time we rebase and force push in our repository.

panva commented 3 years ago

Sorry, we just realized Github is sending a notification here every time we rebase and force push in our repository.

I also get a notification because of the commit which has @panva in the message ;) But don't worry, I don't mind and we're nearing the end of this process anyway.

panva commented 3 years ago

@paragonie-security even with the inclusion of the only viable libsodium bundle for javascript (libsodium-wrappers) I could not get Version 4 implemented. crypto_stream_xchacha20_xor is not available, see the only available methods:

Screenshot 2021-07-30 at 14 53 41

This was merely an exercise on my part, I would not bundle 500kb worth of sodium to this library only to end up with a synchronous execution blocking code anyway.