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

Improved Constructors for Nonce #307

Open
dimitribouniol opened this issue Nov 27, 2024 · 1 comment
Open

Improved Constructors for Nonce #307

dimitribouniol opened this issue Nov 27, 2024 · 1 comment

Comments

@dimitribouniol
Copy link
Contributor

dimitribouniol commented Nov 27, 2024

New API Proposal: Nonce from HKDF Key Derivation

Motivation:

It is quite common to derive a Nonce from the HKDF family of transformations, as outlined in RFC8188 for instance. This came up in the open source slack.

Importance:

Currently, this is possible by deriving a symmetric key, then accessing its unsafeBytes before converting the type to a Nonce. Implementing the above referenced RFC for an implementation of WebPush, this would ultimately look like:

let applicationServerECDHKey = P256.KeyAgreement.PrivateKey()
let payload = try JSONEncoder().encode(alert)

guard
    let userAuthentication = URLEncodedBase64(webPushToken.keys.auth).decodedBytes,
    let userAgentECDHPublicKey = URLEncodedBase64(webPushToken.keys.p256dh).decodedBytes,
    let userAgentECDHKey = try? P256.KeyAgreement.PublicKey(x963Representation: userAgentECDHPublicKey),
    let sharedSecret = try? applicationServerECDHKey.sharedSecretFromKeyAgreement(with: userAgentECDHKey)
else { throw BadSubscription() }

var salt: [UInt8] = Array(repeating: 0, count: 16)
for index in salt.indices { salt[index] = .random(in: .min ... .max) }

let paddedPayloadSize = max(payload.count, 3993)
let paddedPayload = payload + [0x02] + Array(repeating: 0, count: paddedPayloadSize - payload.count)
let recordSize = UInt32(paddedPayload.count + 16)
let keyID = applicationServerECDHKey.publicKey.x963Representation
let keyIDSize = UInt8(keyID.count)

let contentCodingHeader = salt + recordSize.bigEndianBytes + keyIDSize.bigEndianBytes + keyID
let keyInfo = "WebPush: info".utf8Bytes + [0x00] + userAgentECDHKey.x963Representation + applicationServerECDHKey.publicKey.x963Representation
let contentEncryptionKeyInfo = "Content-Encoding: aes128gcm".utf8Bytes + [0x00]
let nonceInfo = "Content-Encoding: nonce".utf8Bytes + [0x00]

let inputKeyMaterial = sharedSecret.hkdfDerivedSymmetricKey(using: SHA256.self, salt: userAuthentication, sharedInfo: keyInfo, outputByteCount: 32)

let contentEncryptionKey = HKDF<SHA256>.deriveKey(inputKeyMaterial: inputKeyMaterial, salt: salt, info: contentEncryptionKeyInfo, outputByteCount: 16)
let nonce = HKDF<SHA256>.deriveKey(inputKeyMaterial: inputKeyMaterial, salt: salt, info: nonceInfo, outputByteCount: 12)

let encryptedRecord = try nonce.withUnsafeBytes { nonceBytes in
    try AES.GCM.seal(paddedPayload, using: contentEncryptionKey, nonce: .init(data: nonceBytes))
}

let requestContent = contentCodingHeader + encryptedRecord.ciphertext + encryptedRecord.tag

However, this sticks out quite a bit against the semantically pleasant deriveKey APIs.

Proposed Improvements

We could either add conversions between SymmetricKey and Nonce, but this feels like the wrong approach. Alternatively, we could define deriveNonce alternatives to the above to directly return a Nonce. Either way, a non-unsafe way to get a nonce suitable for sealing a payload with a given algorithm would be welcome (even better if we could use the new generic integer parameters to correctly determine things like sizing 😍).

@Lukasa
Copy link
Contributor

Lukasa commented Nov 27, 2024

Thanks for filing this! I've brought this up with the CryptoKit team. In the short term, a helper API can be implemented in _CryptoExtras.

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

No branches or pull requests

2 participants