The Trifle SDK is designed to facilitate app-level secure communication between client apps and backend services even across TLS termination points. The SDK establishes a standardized framework that ensures:
- Backend services can verify the authenticity and integrity of the client data
- Availability of authentic client app signals to the backend services
These security guarantees apply on top of transport level security such as TLS and allow application of data safety policies at a finer granularity.
The Trifle SDK integrates with a provided Certificate Authority that provisions a Trifle-formatted certificate to the client app. This certificate captures curently only client app identity.
There are two main components to Trifle SDK:
- Standardized convenience APIs that provide recommended established security measures on given native platforms, abstracting away implementation complexities.
- Standardize message formatting for security artifacts including certificates, ciphertexts and signatures.
Trifle SDK is implemented on client side in iOS and Android and on server side in Kotlin.
// App start up
// The access group value must be added to the App Groups [entitlement file](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups).
let trifle = try Trifle(reverseDomain: abc, accessGroup: "group.trifle.cash.app")
// Check if a key already exists.
// If no key exists, generate a public key pair
let keyHandle = try trifle.generateKeyHandle()
// Storing keys. Keys are codable.
let encoder = JSONEncoder()
let jsonKeyHandle = try encoder.encode(keyHandle)
// Load the key from storage when we need to use it
let decoder = JSONDecoder()
let decoded = try decoder.decode(TrifleKeyHandle.self, from: jsonKeyHandle)
// Check the validity of loaded key
let valid = trifle.isValid(keyHandle: keyHandle)
// Destroy key that is no longer in use or is invalid
let status = trifle.delete(keyHandle: keyHandle)
// Destroy key that is no longer in use or is invalid
let status = trifle.delete(keyHandle: keyHandle)
// Check if loaded key already has a cert. If yes, skip to checking for cert validity
// Else if key does not have a cert OR if a new cert must be generated (eg because of existing
// cert is already expired, or app needs to re-attest, app is re-installed, app is restored
// from backup, ... etc)
// Create cert request
let certReq = try trifle.generateMobileCertificateRequest(entity: entity, keyHandle: keyHandle)
// Serialize to proto to be sent over wire
let encoded = try certReq.serialize()
// Send certificate request to Certificate Authority endpoint. Response will be [Data]
let response: [Data]
// Iterate over each Data to convert to TrifleCertificate
let certs = try response.map({ try TrifleCertificate.deserialize(data: $0) })
// certs is an array of certificates where [0] will be device certificate
// and the rest of the elements will be intermediate chain.
// For the following, verify API can throw a number of errors as defined by TrifleError
// Check if app has the root cert of Certificate Authority (CA).
// Validate cert matches the certificate request (so generated key)
// and the root (so it has been generated by the right CA).
let isValid = certs[0].verify(
certificateRequest: certRequest,
intermediateTrifleChain: certs,
rootTrifleCertificate: root)
// Once it passes validation, certReq is no longer needed and it can be deleted
// Store cert along with the respective keyHandle
// To check only for the validity of a stored cert, you can do either of below choices
// Option 1 is a more complete check of the device cert and the full chain
isValid = certs[0].verify(intermediateChain: certs )
// option 2 only checks the validity of the device cert
isValid = certs[0].verify(intermediateChain: [] )
// Sign the data
let trifleSignedData = try trifle.createSignedData(
data: dataThatIsSigned,
keyHandle: keyHandle,
certificates: certs )
// Serialize to proto to be sent over wire
let encodedTrifleSignedDataProto = try trifleSignedData.serialize()
// App start up
val trifleApi = TrifleApi(reverseDomain = "abc")
// Check if a key already exists.
// If no key exists, generate a public key pair
var keyHandle: KeyHandle = trifleApi.generateKeyHandle()
// Storing keys. Key handles are serializable.
val encoded: ByteArray = keyHandle.serialize()
// Load the key from storage when we need to use it
val decoded: KeyHandle = KeyHandle.deserialize(encoded)
// Check the validity of loaded key
val valid: Boolean = trifleApi.isValid(keyHandle)
// Destroy key that is no longer in use or is invalid
trifleApi.delete(keyHandle)
// Check if loaded key already has a cert. If yes, skip to checking for cert validity
// Else if key does not have a cert OR if a new cert must be generated (eg because of existing
// cert is already expired, or app needs to re-attest, app is re-installed, app is restored
// from backup, ... etc)
// Create cert request with an entity identity that is associated with the public key
// Once you receive the certificate, you can delete the cert request.
val certReq: CertificateRequest = trifleApi.generateMobileCertificateRequest(entity, keyHandle)
// Serialize as an opaque ByteArray
val encoded: ByteArray = certReq.serialize()
// Send certificate request to Certificate Authority endpoint. Response will be List<ByteArray>
val response: List<ByteArray>
// Iterate over each ByteArray to convert to Certificate
val certs: List<Certificate> = response.map { Certificate.deserialize(it) }
// certs is a list of certificates where [0] will be device certificate
// and the rest of the elements will be intermediate chain.
// Check if app has the root cert of Certificate Authority (CA).
// Verify that the cert chain validates OK (i.e., it has been generated by the right CA).
val result: Result<Unit> = trifleApi.verifyChain(certs, root)
// Store cert along with the respective keyHandle
// To check only for the validity of a stored cert, you can do either of below choices
// Option 1 is a more complete check of the device cert and the full chain
val result: Result<Unit> = trifleApi.verifyChain(certs)
// option 2 only checks the validity of the device cert
val result: Result<Unit> = trifleApi.verifyValidity(cert)
// Error handling from the Result.isFailure can be found in the enumeration in TrifleErrors
if (result.isFailure) {
when (result.getExceptionOrNull) {
is NoTrustAnchor -> {} // trust anchor is missing
is InvalidCertPath -> {} // certificate path is invalid
is ExpiredCertificate -> {} // certificate has expired
is InvalidSignature -> {} // signature did not match
is CSRMismatch -> {} // attributes in the cert did not match CSR
else -> {} // unspecified failure
}
}
// Sign the data
val signedData: SignedData = trifleApi.createSignedData(clientData, keyHandle, certs)
// Serialize as an opaque ByteArray
val encodedSignedData: ByteArray = signedData.serialize()
TBD
TBD
The Trifle roadmap includes some of the following features:
- Backend services are able to exchange private messages with the client app
- Inclusion of client provided attributes and/or user information in the certificate
- Extending client SDK to Web