Skip to content

Commit

Permalink
Implement NanoTDF creation and adjustment of encryption payload
Browse files Browse the repository at this point in the history
New functions for creating a Nano Document Format (NanoTDF) and adjusting the encryption payload have been added. This reflects changes in the Policy, EmbeddedPolicyBody, and KasMetadata structures. An adjustNonce method has also been introduced to pad or trim the nonce to the required length. Besides, the impact on related tests and other classes has been managed.
  • Loading branch information
arkavo-com committed Jun 10, 2024
1 parent 608cc31 commit 75a7a27
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 35 deletions.
4 changes: 4 additions & 0 deletions NanoTDF.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
DA07A6952C165A83003DC210 /* CryptoHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA07A6942C165A83003DC210 /* CryptoHelper.swift */; };
DA07A6972C165ABB003DC210 /* CryptoHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA07A6962C165ABB003DC210 /* CryptoHelperTests.swift */; };
DA07A6992C166E7E003DC210 /* BinaryParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA07A6982C166E7E003DC210 /* BinaryParser.swift */; };
DA07A69B2C167904003DC210 /* NanoTDFCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA07A69A2C167904003DC210 /* NanoTDFCreationTests.swift */; };
DA2B7B1B2BF1BB19002F3150 /* NanoTDFTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2B7B162BF1BB19002F3150 /* NanoTDFTests.swift */; };
DA2B7B1E2BF1BB19002F3150 /* WebSocketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2B7B192BF1BB19002F3150 /* WebSocketManager.swift */; };
DA3C23F42BE0765100B4B883 /* NanoTDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA3C23F32BE0765100B4B883 /* NanoTDF.swift */; };
Expand Down Expand Up @@ -43,6 +44,7 @@
DA07A6942C165A83003DC210 /* CryptoHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoHelper.swift; sourceTree = "<group>"; };
DA07A6962C165ABB003DC210 /* CryptoHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoHelperTests.swift; sourceTree = "<group>"; };
DA07A6982C166E7E003DC210 /* BinaryParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryParser.swift; sourceTree = "<group>"; };
DA07A69A2C167904003DC210 /* NanoTDFCreationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NanoTDFCreationTests.swift; sourceTree = "<group>"; };
DA2B7B162BF1BB19002F3150 /* NanoTDFTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NanoTDFTests.swift; sourceTree = "<group>"; };
DA2B7B192BF1BB19002F3150 /* WebSocketManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocketManager.swift; sourceTree = "<group>"; };
DA3C23F02BE0765100B4B883 /* libNanoTDF.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libNanoTDF.a; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -77,6 +79,7 @@
DABEB41F2C160B61004540E6 /* InitializationTests.swift */,
DA2B7B192BF1BB19002F3150 /* WebSocketManager.swift */,
DA07A6962C165ABB003DC210 /* CryptoHelperTests.swift */,
DA07A69A2C167904003DC210 /* NanoTDFCreationTests.swift */,
);
path = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -213,6 +216,7 @@
DA2B7B1B2BF1BB19002F3150 /* NanoTDFTests.swift in Sources */,
DABEB4202C160B61004540E6 /* InitializationTests.swift in Sources */,
DA07A6972C165ABB003DC210 /* CryptoHelperTests.swift in Sources */,
DA07A69B2C167904003DC210 /* NanoTDFCreationTests.swift in Sources */,
DA2B7B1E2BF1BB19002F3150 /* WebSocketManager.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
30 changes: 15 additions & 15 deletions NanoTDF/BinaryParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ class BinaryParser {
print("Failed to read Remote Policy binding")
return nil
}
return Policy(type: .remote, body: nil, remote: resourceLocator, binding: binding, keyAccess: nil)
return Policy(type: .remote, body: nil, remote: resourceLocator, binding: binding)
case .embeddedPlaintext, .embeddedEncrypted, .embeddedEncryptedWithPolicyKeyAccess:
let policyData = readEmbeddedPolicyBody(policyType: policyType, bindingMode: bindingMode)
// Binding
guard let binding = readPolicyBinding(bindingMode: bindingMode) else {
print("Failed to read Remote Policy binding")
return nil
}
return Policy(type: .embeddedPlaintext, body: policyData?.plaintextCiphertext, remote: nil, binding: binding, keyAccess: policyData?.policyKeyAccess)
return Policy(type: .embeddedPlaintext, body: policyData, remote: nil, binding: binding)
}
}

Expand All @@ -80,7 +80,7 @@ class BinaryParser {
// if no policy added then no read
// Note 3.4.2.3.2 Body for Embedded Policy states Minimum Length is 1
if contentLength == 0 {
return EmbeddedPolicyBody(contentLength: contentLength, plaintextCiphertext: nil, policyKeyAccess: nil)
return EmbeddedPolicyBody(length: 1, body: Data([0x00]), keyAccess: nil)
}

guard let plaintextCiphertext = read(length: Int(contentLength)) else {
Expand All @@ -89,7 +89,7 @@ class BinaryParser {
}
let keyAccess = policyType == .embeddedEncryptedWithPolicyKeyAccess ? readPolicyKeyAccess(bindingMode: bindingMode) : nil

return EmbeddedPolicyBody(contentLength: contentLength, plaintextCiphertext: plaintextCiphertext, policyKeyAccess: keyAccess)
return EmbeddedPolicyBody(length: plaintextCiphertext.count, body: plaintextCiphertext, keyAccess: nil)
}

func readEccAndBindingMode() -> PolicyBindingConfig? {
Expand All @@ -101,18 +101,18 @@ class BinaryParser {
}
let eccModeHex = String(format: "%02x", eccAndBindingMode)
print("ECC Mode Hex:", eccModeHex)
let bound = (eccAndBindingMode & (1 << 7)) != 0
let ecdsaBinding = (eccAndBindingMode & (1 << 7)) != 0
let ephemeralECCParamsEnumValue = Curve(rawValue: eccAndBindingMode & 0x7)

guard let ephemeralECCParamsEnum = ephemeralECCParamsEnumValue else {
print("Unsupported Ephemeral ECC Params Enum value")
return nil
}

print("bound: \(bound)")
print("ecdsaBinding: \(ecdsaBinding)")
print("ephemeralECCParamsEnum: \(ephemeralECCParamsEnum)")

return PolicyBindingConfig(ecdsaBinding: bound, curve: ephemeralECCParamsEnum)
return PolicyBindingConfig(ecdsaBinding: ecdsaBinding, curve: ephemeralECCParamsEnum)
}

func readSymmetricAndPayloadConfig() -> SignatureAndPayloadConfig? {
Expand Down Expand Up @@ -211,15 +211,15 @@ class BinaryParser {
}

func parsePayload(config: SignatureAndPayloadConfig) throws -> Payload {
guard let lengthData = read(length: FieldSize.payloadCipherTextSize)
guard let lengthData = read(length: FieldSize.payloadLengthSize)
else {
throw ParsingError.invalidFormat
}
var length: UInt32 = 0
let count = lengthData.count
for i in 0 ..< count {
length += UInt32(lengthData[i]) << (8 * (count - 1 - i))
}
let byte1 = UInt32(lengthData[0]) << 16
let byte2 = UInt32(lengthData[1]) << 8
let byte3 = UInt32(lengthData[2])
let length: UInt32 = byte1 | byte2 | byte3
print("parsePayload length", length)
// IV nonce
guard let iv = read(length: FieldSize.payloadIvSize)
else {
Expand Down Expand Up @@ -249,7 +249,7 @@ class BinaryParser {
guard let ciphertext = read(length: cipherTextLength),
let payloadMAC = read(length: payloadMACSize)
else {
throw ParsingError.invalidFormat
throw ParsingError.invalidPayload
}
let payload = Payload(length: length, iv: iv, ciphertext: ciphertext, mac: payloadMAC)
return payload
Expand Down Expand Up @@ -300,7 +300,7 @@ enum FieldSize {
static let maxPolicySize = 257
static let minEphemeralKeySize = 33
static let maxEphemeralKeySize = 133
static let payloadCipherTextSize = 3
static let payloadLengthSize = 3
static let payloadIvSize = 3
static let minPayloadMacSize = 8
static let maxPayloadMacSize = 32
Expand Down
19 changes: 19 additions & 0 deletions NanoTDF/CryptoHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,32 @@ enum CryptoHelper {
return symmetricKey
}

// Generate GMAC tag for the policy body
static func createGMACBinding(policyBody: Data, symmetricKey: SymmetricKey) throws -> Data {
let gmac = try AES.GCM.seal(policyBody, using: symmetricKey)
return gmac.tag
}

// Step 5: Generate nonce (IV)
static func generateNonce(length: Int = 12) -> Data {
var nonce = Data(count: length)
_ = nonce.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, length, $0.baseAddress!) }
return nonce
}

// Pad or trim nonce (IV) to the required length
static func adjustNonce(_ nonce: Data, to length: Int) -> Data {
if nonce.count == length {
return nonce
} else if nonce.count > length {
return nonce.prefix(length)
} else {
var paddedNonce = nonce
paddedNonce.append(contentsOf: [UInt8](repeating: 0, count: length - nonce.count))
return paddedNonce
}
}

// Step 6: Encrypt payload using symmetric key and nonce (IV)
static func encryptPayload(plaintext: Data, symmetricKey: SymmetricKey, nonce: Data) throws -> (ciphertext: Data, tag: Data) {
let sealedBox = try AES.GCM.seal(plaintext, using: symmetricKey, nonce: AES.GCM.Nonce(data: nonce))
Expand Down
114 changes: 98 additions & 16 deletions NanoTDF/NanoTDF.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ struct Payload {

func toData() -> Data {
var data = Data()
let lengthBytes = withUnsafeBytes(of: length.bigEndian) { Array($0) }
data.append(contentsOf: lengthBytes[1 ... 3]) // Append the last 3 bytes to represent a 3-byte length
data.append(UInt8((length >> 16) & 0xFF))
data.append(UInt8((length >> 8) & 0xFF))
data.append(UInt8(length & 0xFF))
data.append(iv)
data.append(ciphertext)
data.append(mac)
Expand Down Expand Up @@ -166,14 +167,14 @@ struct Policy {
case remote = 0x00
case embeddedPlaintext = 0x01
case embeddedEncrypted = 0x02
// IV value 00 00 00 is reserved for use with an encrypted policy.
case embeddedEncryptedWithPolicyKeyAccess = 0x03
}

let type: PolicyType
let body: Data?
let body: EmbeddedPolicyBody?
let remote: ResourceLocator?
let binding: Data?
let keyAccess: PolicyKeyAccess?
var binding: Data?

func toData() -> Data {
var data = Data()
Expand All @@ -185,10 +186,7 @@ struct Policy {
}
case .embeddedPlaintext, .embeddedEncrypted, .embeddedEncryptedWithPolicyKeyAccess:
if let body = body {
data.append(body)
}
if let keyAccess = keyAccess {
data.append(keyAccess.toData())
data.append(body.toData())
}
}
if let binding = binding {
Expand All @@ -199,9 +197,19 @@ struct Policy {
}

struct EmbeddedPolicyBody {
let contentLength: UInt16
let plaintextCiphertext: Data?
let policyKeyAccess: PolicyKeyAccess?
let length: Int
let body: Data
let keyAccess: PolicyKeyAccess?

func toData() -> Data {
var data = Data()
data.append(UInt8(body.count)) // length
data.append(body)
if let keyAccess = keyAccess {
data.append(keyAccess.toData())
}
return data
}
}

struct PolicyKeyAccess {
Expand Down Expand Up @@ -291,15 +299,14 @@ func initializeSmallNanoTDF(kasResourceLocator: ResourceLocator) -> NanoTDF {
curve: curve),
payloadSigMode: SignatureAndPayloadConfig(signed: false,
signatureCurve: nil,
payloadCipher: .aes256GCM64),
payloadCipher: .aes256GCM128),
policy: Policy(type: .embeddedPlaintext,
body: nil,
remote: nil,
binding: nil,
keyAccess: nil),
binding: nil),
ephemeralKey: Data([0x04, 0x05, 0x06]))

let payload = Payload(length: 1,
let payload = Payload(length: 7,
iv: Data([0x07, 0x08, 0x09]),
ciphertext: Data([0x00]),
mac: Data([0x13, 0x14, 0x15]))
Expand All @@ -308,3 +315,78 @@ func initializeSmallNanoTDF(kasResourceLocator: ResourceLocator) -> NanoTDF {
payload: payload,
signature: nil)
}

struct KasMetadata {
let resourceLocator: ResourceLocator
let publicKey: Any
let curve: Curve
}

func createNanoTDF(kas: KasMetadata, policy: inout Policy, plaintext: Data) throws -> NanoTDF {
// Step 1: Generate an ephemeral key pair
guard let (ephemeralPrivateKey, ephemeralPublicKey) = CryptoHelper.generateEphemeralKeyPair(curveType: kas.curve) else {
throw NSError(domain: "CryptoError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to generate ephemeral key pair"])
}

// Step 2: Derive shared secret
guard let sharedSecret = try CryptoHelper.deriveSharedSecret(curveType: kas.curve, ephemeralPrivateKey: ephemeralPrivateKey, recipientPublicKey: kas.publicKey) else {
throw NSError(domain: "CryptoError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to derive shared secret"])
}

// Step 3: Derive symmetric key
let symmetricKey = CryptoHelper.deriveSymmetricKey(sharedSecret: sharedSecret)
var policyBody: Data
switch policy.type {
case .remote:
policyBody = policy.remote!.toData()
case .embeddedPlaintext, .embeddedEncrypted, .embeddedEncryptedWithPolicyKeyAccess:
policyBody = policy.body!.toData()
}
let gmacTag = try CryptoHelper.createGMACBinding(policyBody: policyBody, symmetricKey: symmetricKey)
policy.binding = gmacTag
// Step 4: Generate nonce (IV)
// 3.3.2.2 IV + Ciphertext + MAClength 3
let nonce3 = CryptoHelper.adjustNonce(CryptoHelper.generateNonce(), to: 3)
let nonce12 = CryptoHelper.adjustNonce(nonce3, to: 12)

// Step 5: Encrypt payload
let (ciphertext, tag) = try CryptoHelper.encryptPayload(plaintext: plaintext, symmetricKey: symmetricKey, nonce: nonce12)

// Step 6: Create Policy Key Access structure
// let policyKeyAccessEphemeralKeyPair = CryptoHelper.generateEphemeralKeyPair(curveType: kas.curve)!
// let policyKeyAccess = PolicyKeyAccess(
// resourceLocator: kas.resourceLocator,
// ephemeralPublicKey: policyKeyAccessEphemeralKeyPair.publicKey
// )

// If including nonce in payload, add its length
let payloadLength = ciphertext.count + tag.count + nonce3.count
print("createNanoTDF payloadLength", payloadLength)
// Payload
let payload = Payload(length: UInt32(payloadLength),
iv: nonce3,
ciphertext: ciphertext,
mac: tag)
// Header
let magicNumber = Data([0x4C, 0x31]) // 0x4C31 (L1L) - first 18 bits
let version = Data([0x4C]) // version[0] & 0x3F (12) last 6 bits for version
let curve: Curve = .secp256r1
var ephemeralPublicKeyData: Data = Data()
if let ephemeralPublicKey = ephemeralPublicKey as? P256.KeyAgreement.PublicKey {
ephemeralPublicKeyData = ephemeralPublicKey.x963Representation
}
print("ephemeralPublicKeyData.count", ephemeralPublicKeyData.count)
let header = Header(magicNumber: magicNumber,
version: version,
kas: kas.resourceLocator,
eccMode: PolicyBindingConfig(ecdsaBinding: false,
curve: curve),
payloadSigMode: SignatureAndPayloadConfig(signed: false,
signatureCurve: .secp256r1,
payloadCipher: .aes256GCM128),
policy: policy,
ephemeralKey: ephemeralPublicKeyData)
return NanoTDF(header: header!,
payload: payload,
signature: nil)
}
4 changes: 4 additions & 0 deletions Tests/CryptoHelperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ final class CryptoHelperTests: XCTestCase {
// Step 4: Derive symmetric key
let symmetricKey = CryptoHelper.deriveSymmetricKey(sharedSecret: sharedSecret)
print("Symmetric Key: \(symmetricKey)")
// Create GMAC binding for the policy body
let policyBody = "classification:secret".data(using: .utf8)!
let gmacTag = try CryptoHelper.createGMACBinding(policyBody: policyBody, symmetricKey: symmetricKey)
print("GMAC Tag: \(gmacTag.base64EncodedString())")
// Step 5: Generate nonce (IV)
let nonce = CryptoHelper.generateNonce()
print("Nonce (IV): \(nonce)")
Expand Down
5 changes: 2 additions & 3 deletions Tests/InitializationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class InitializationTests: XCTestCase {
XCTAssertEqual(nanoTDF.header.kas.protocolEnum, locator!.protocolEnum)
XCTAssertEqual(nanoTDF.header.kas.body, locator!.body)
// Validate the Payload
XCTAssertEqual(nanoTDF.payload.length, 1)
XCTAssertEqual(nanoTDF.payload.length, 7)
XCTAssertEqual(nanoTDF.payload.iv, Data([0x07, 0x08, 0x09]))
// As there signature is nil in this scenario
XCTAssertNil(nanoTDF.signature)
Expand All @@ -53,8 +53,7 @@ final class InitializationTests: XCTestCase {
policy: Policy(type: .embeddedPlaintext,
body: nil,
remote: nil,
binding: nil,
keyAccess: nil),
binding: nil),
ephemeralKey: Data([0x04, 0x05, 0x06]))
XCTAssertNil(header)
}
Expand Down
Loading

0 comments on commit 75a7a27

Please sign in to comment.