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

Encrypted keys should provide a public key to decrypt encoded metadata #178

Closed
ezekg opened this issue Feb 6, 2018 · 16 comments
Closed

Comments

@ezekg
Copy link
Member

ezekg commented Feb 6, 2018

Encrypted keys should offer the ability to encode customer data within the license key, e.g. user email, name, etc. and should offer a public key which can be used to decode the data client-side. A private key would need to be generated alongside every encrypted policy, and encoded fields should be selectable by the account admin.

This would make encrypted keys more useful for e.g. offline use, where the public key can be stored client-side for offline license key validation.

@ezekg
Copy link
Member Author

ezekg commented Feb 6, 2018

Related to #173.

@ezekg
Copy link
Member Author

ezekg commented Feb 6, 2018

Convo with the founder of Timing,

Zeke:
I've been throwing around an idea of generating encrypted licenses that can be populated with metadata (so similar to your protobufs, containing email, name, etc.), and then each account gets a pub key generated that they can use to decrypt the license offline. Additional validation concerning machines has to be done over the wire, but you get a good base-case for offline use.

With an SDK and that—would something like that be valuable to you?

Daniel:
Yes, that sounds like what I'd want. I was just about to say "you're mostly there — slap a signature onto your licenses and give me an SDK to request and consume them in a convenient, somewhat tamper-proof manner (that's where the source SDK comes in), and you have it"

@ezekg
Copy link
Member Author

ezekg commented Feb 6, 2018

I've removed mentions of the old encrypted licensing scheme from the docs. The old scheme is being used, so we'll need to add logic to account for older policies using the v1 scheme and continue to generate licenses with that scheme and not the new one. We can add an attribute to the policy like encryptionScheme: 'v1' or lock the scheme based on the policy's creation date.

@ezekg ezekg changed the title Encrypted keys should provide a public key to decrypt encoded data Encrypted keys should provide a public key to decrypt encoded metadata Feb 6, 2018
@ezekg
Copy link
Member Author

ezekg commented Feb 6, 2018

Add an attribute like encryptionFields: ['user.id', 'user.email', 'user.metadata.customerId'] to specify which fields get encoded into the license key, and in what order.

Or maybe just a scalar hash like metadata e.g. encryptionData: { userId: 1, userEmail: '[email protected]', customerId: 'cus_0123456789' }.

@ezekg
Copy link
Member Author

ezekg commented Feb 21, 2018

Encryption fields should not be able to be changed after creation.

@ezekg
Copy link
Member Author

ezekg commented Feb 22, 2018

Having second thoughts on this, as it introduces a lot of complexity. May just write an example implementation in Swift using Keygen and protobufs for offline license support.

@ezekg
Copy link
Member Author

ezekg commented Jul 3, 2018

I think the easiest way to implement this would be to allow an arbitrary string to be passed as a key, like we're doing now, and then encrypt the key with the account's private key. The encrypted key can then be decoded using the account's public key to retrieve any encoded data, e.g. JSON, etc.

We need to prioritize, as this would allow offline licensing to be easier to implement.

@ezekg
Copy link
Member Author

ezekg commented Jul 30, 2018

Max key size should be 200 bytes, since RSA 2048 has a max data size of 245 bytes.

@ezekg
Copy link
Member Author

ezekg commented Jul 30, 2018

Thought: Maybe use AES for encryptionScheme v3? Will allow larger datasets to be encrypted.

@ezekg
Copy link
Member Author

ezekg commented Jul 31, 2018

To support large license key data, we need to use AES. I think we should allow multiple encryption schemes, so the customer can opt-in to whichever fits their use case the best:

  • encryptionScheme = 'AES'
  • encryptionScheme = 'RSA'

And then encryptionScheme = null means it's using v1 (hashing). We will need to update the account's publicKey meta to be rsaPublicKey, and then add aesKey and aesIv.

It should not be able to be changed.

@ezekg
Copy link
Member Author

ezekg commented Jul 31, 2018

Using AES:

require 'openssl'
require 'base64'

data = "Very, very confidential data\n" * 100

cipher = OpenSSL::Cipher::AES256.new :CBC
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv

encrypted = cipher.update(data) + cipher.final

decipher = OpenSSL::Cipher::AES256.new :CBC
decipher.decrypt
decipher.key = key
decipher.iv = iv

plain = decipher.update(encrypted) + decipher.final

puts "key = #{Base64.strict_encode64(key)}"
puts "iv = #{Base64.strict_encode64(iv)}"
puts "match = #{data == plain}"

@ezekg
Copy link
Member Author

ezekg commented Aug 5, 2018

I don’t like the idea of using AES, actually. It opens up the possibility of keygens being created, and rather easily at that. Maybe we need to use 4096-bit RSA keys?

@ezekg
Copy link
Member Author

ezekg commented Aug 5, 2018

Or we could just sign keys and skip embedded metadata… or add a separate signed attribute?

(We should do this later on.)

@ezekg
Copy link
Member Author

ezekg commented Aug 9, 2018

Potential customer, Serveo, who seems to want the signed keys (good validation to keep it).

@ezekg
Copy link
Member Author

ezekg commented Aug 9, 2018

Example setup for showcasing signed keys:

Server

const crypto = require('crypto')

// License payload
const payload = {
  key: crypto.randomBytes(16).toString('hex'),
  customer: '[email protected]',
  allowances: 3
}

// Convert JSON key payload into a buffer and then encode into base64
const key = Buffer.from(JSON.stringify(payload)).toString('base64')

// TODO(ezekg) Store license key in Keygen using the RSA_2048_SIGN encryption
//             scheme, then encode into a license file along with the key's
//             generated RSA signature for verification purposes.
const sig = ''

// Combine key and signature into a "license file"
const licenseFile = `${key}:${sig}`

// TODO(ezekg) Deliver license file to customer
const hex = Buffer.from(licenseFile).toString('hex')

Client

const { KEYGEN_PUBLIC_KEY } = process.env
const crypto = require('crypto')
const chalk = require('chalk')

// Decode the license file's contents
const [key, sig] = licenseFile.split(':')

// Verify the license key's contents
try {
  const verifier = crypto.createVerify('sha256')
  verifier.write(key)
  verifier.end()

  const v = verifier.verify(KEYGEN_PUBLIC_KEY, sig, 'base64')
  if (!v) {
    throw new Error('License key failed signature verification')
  }
} catch (e) {
  console.error(
    chalk.red(e.message)
  )

  process.exit(1)
}

// Decode JSON key payload
const buf = Buffer.from(key, 'base64')
const obj = JSON.parse(buf.toString())

console.log(
  chalk.green(JSON.stringify(obj, null, 2))
)

@ezekg
Copy link
Member Author

ezekg commented Aug 19, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant