A Kotlin wrapper/library for notarizing applications for macOS, using Apple's Notary Web Service REST API. This library tries to follow the Notary API, as closely as possible, mapping methods to the Endpoint calls. It also provides some convenience methods that groups several Endpoint calls together.
This project is in Beta and its API may be unstable.
This project adheres to Semantic Versioning 2.0.0.
This project is not currently accepting any pull requests. If you find a problem, or have a suggestion please log an issue.
This library is available on Maven Central.
<dependency>
<groupId>
ca.ewert-technologies.notarytoolkotlin
</groupId>
<artifactId>
notarytool-kotlin
</artifactId>
<version>
0.2.1
</version>
</dependency>
implementation "ca.ewert-technologies.notarytoolkotlin:notarytool-kotlin:0.2.1"
Please refer to the notarytool-kotlin API Docs for complete documentation details.
To be able to notarize an application for macOS, you need to have an Apple Developer account, with access to App Store Connect. In order to make calls to the Notary API, you first need to create an API Key; see: Creating API Keys for App Store Connect API for instructions. After creating an API key, you should have the following information:
Item | Description |
---|---|
Issuer ID | Your issuer ID from the API Keys page in App Store Connect |
Private Key ID | Your Private key ID from App Store Connect |
Private Key | The .p8 file downloaded when creating the API Key |
These items are used to generate a JSON Web Token (JWT), which is used for authentication when making calls to the Notary API (see Generating Tokens for API Requests for more information). Note: the JWT is generated for you automatically by this library.
All calls to the Notary API are done using the NotaryToolClient
class. A NotaryToolClient
object should only be
created once, subsequent calls should use the same instance.
This example shows creating a NotaryToolClient
instance using the Authentication information obtained as above, and
using the default configuration.
val notaryToolClient = NotaryToolClient(
privateKeyId = "<Private Key ID>",
issuerId = "<Issuer ID here>",
privateKeyFile = Path.of(
"path",
"to",
"privateKeyFile.p8"
),
)
This example shows creating a NotaryToolClient
instance using the Authentication information obtained as above, but
using a function that supplies an ECPrivateKey
based on the Private Key file.
val notaryToolClient = NotaryToolClient(
privateKeyId = "<Private Key ID>",
issuerId = "<Issuer ID here>",
privateKeyProvider = ::privateKeyProvider
)
)
...
/**
* Creates an ECPrivateKey.
*
* @return an ECPrivateKey or a JsonWebTokenError
*/
internal fun privateKeyProvider(): Result<ECPrivateKey, JsonWebTokenError> {
// Custom code that creates an ECPrivateKey
}
This example shows creating a NotaryToolClient
using the Authentication information obtained as above, and using
custom parameters.
val notaryToolClient = NotaryToolClient(
privateKeyId = "<Private Key ID>",
issuerId = "<Issuer ID here>",
privateKeyFile = Path.of(
"path",
"to",
"privateKeyFile.p8"
),
tokenLifetime = Duration.of(
10,
ChronoUnit.MINUTES
),
connectTimeout = Duration.of(
15,
ChronoUnit.SECONDS
),
userAgent = "MyTool/1.0.0"
)
tokenLifetime
sets how long the JWT is valid for. Apple requires this to be less than 20 minutes. The default is 15 minutes.connectTimeout
sets the HTTP connection timeout. The default is 10 seconds.userAgent
sets the"User-Agent"
to use as part of the request. The default value is:notarytool-kotlin/x.y.z
wherex.y.z
is the current version of the library.
The NotaryToolClient
has the following methods:
Function | Notes |
---|---|
submitSoftware() |
Starts the process of uploading a new version of your software to the notary service. Maps to the Submit Software Endpoint. |
getSubmissionStatus() |
Fetches the status of a software notarization submission. Maps to the Get Submission Status Endpoint. |
getSubmissionLog() |
Fetches details about a single completed notarization. Maps to the Get Submission Log Endpoint. |
getPreviousSubmissions() |
Fetches a list of your team’s previous notarization submissions. Maps to the Get Previous Submissions Endpoint. |
submitAndUploadSoftware() |
A convenience method that calls submitSoftware() and then uploads the software to the Amazon S3 Server, where the Notary API can access it. |
pollSubmissionStatus() |
A convenience method that repeatedly calls getSubmissionStatus() , until the status is no longer In Progress . |
retrieveSubmissionLog() |
A convenience method that calls getSubmissionLog() and returns the log as String. |
downloadSubmssionLog() |
A convenience method that calls getSubmissionLog() and downloads the log as file. |
Please refer to the notarytool-kotlin API Docs for complete documentation details.
Errors are handled using a Result type, using the kotlin-result library.
The Result type, has two subtypes, OK<V>
, which represents success, and contains the value to be returned, and
Err<E>
, which represents failure, and contains an NotaryToolError
subtype. In general, when a function returns a
Result type, the Result will either contain the success value, or it will contain an error type. See below for
some examples.
This example uses submitAndUploadSoftware()
to initiate a submission request, and then upload the software, so it
can be notarized. The returned Result contains the submission id, which can later be used to check the notarization
status.
val notaryToolClient = NotaryToolClient(
privateKeyId = "<Private Key ID>",
issuerId = "<Issuer ID here>",
privateKeyFile = Path.of(
"path",
"to",
"privateKeyFile.p8"
),
)
val softwareFile: Path = Path.of("softwareApp.dmg")
val result: Result<AwsUploadData, NotaryToolError> = notaryToolClient.submitAndUploadSoftware(softwareFile)
result.onSuccess { awsUploadData ->
println("Uploaded file: ${softwareFile.fileName}, and received submissionId: ${awsUploadData.submissionId}")
}
result.onFailure { notaryToolError ->
println(notaryToolError.longMsg)
}
This would return output similar to:
Uploaded file: softwareApp.dmg, and received submissionId: 6253220d-ee59-4682-8d23-9a4fe87eaagg
This example uses getSubmissionStatus()
, to check the status of a previous submission.
val submissionIdResult: Result<SubmissionId, NotaryToolError.UserInputError.MalformedSubmissionIdError> =
SubmissionId.of("6253220d-ee59-4682-8d23-9a4fe87eaagg")
submissionIdResult.onSuccess { submissionId ->
val notaryToolClient = NotaryToolClient(
privateKeyId = "<Private Key ID>",
issuerId = "<Issuer ID here>",
privateKeyFile = Path.of(
"path",
"to",
"privateKeyFile.p8"
),
)
val result: Result<SubmissionStatusResponse, NotaryToolError> = notaryToolClient.getSubmissionStatus(submissionId)
result.onSuccess { submissionStatusResponse ->
val statusInfo = submissionStatusResponse.submissionInfo
println("Status for submission $submissionId (${statusInfo.name}): ${statusInfo.status}")
}
result.onFailure { notaryToolError ->
println(notaryToolError.longMsg)
}
}
submissionIdResult.onFailure { malformedSubmissionIdError ->
println(malformedSubmissionIdError)
}
This would return output similar to:
Status for submission 6253220d-ee59-4682-8d23-9a4fe87eaagg (softwareApp.dmg): Accepted
Other Kotlin examples can be found in: Kotlin examples. Examples for use with Java can be found in: Java examples.
For any issues or suggestions please use GitHub Issues.
This project uses the following runtime and test dependencies.
- koltin-result
- okhttp
- java-jwt
- moshi
- aws-sdk
- kotlin-logging
- slf4j
- logback
- commons-lang3
- junit5
- assertk
- mockwebserver
See also licenses.txt file.
This project is licensed under the MIT License.