Merge branch 'main' of github.com:maxgoedjen/secretive into newsetup

This commit is contained in:
Max Goedjen 2025-09-03 00:18:48 -07:00
commit 981cf3d2d6
No known key found for this signature in database
8 changed files with 25 additions and 23 deletions

View File

@ -61,4 +61,4 @@ Because secrets in the Secure Enclave are not exportable, they are not able to b
## Security ## Security
If you discover any vulnerabilities in this project, please notify [max.goedjen@gmail.com](mailto:max.goedjen@gmail.com) with the subject containing "SECRETIVE SECURITY." Secretive's security policy is detailed in [SECURITY.md](SECURITY.md). To report security issues, please use [GitHub's private reporting feature.](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability)

View File

@ -24,4 +24,4 @@ The latest version on the [Releases page](https://github.com/maxgoedjen/secretiv
## Reporting a Vulnerability ## Reporting a Vulnerability
If you discover any vulnerabilities in this project, please notify max.goedjen@gmail.com with the subject containing "SECRETIVE SECURITY." To report security issues, please use [GitHub's private reporting feature.](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability)

View File

@ -89,9 +89,8 @@ extension Agent {
for secret in secrets { for secret in secrets {
let keyBlob = publicKeyWriter.data(secret: secret) let keyBlob = publicKeyWriter.data(secret: secret)
let curveData = publicKeyWriter.openSSHIdentifier(for: secret.keyType)
keyData.append(keyBlob.lengthAndData) keyData.append(keyBlob.lengthAndData)
keyData.append(curveData.lengthAndData) keyData.append(publicKeyWriter.comment(secret: secret).lengthAndData)
count += 1 count += 1
if let (certificateData, name) = try? await certificateHandler.keyBlobAndName(for: secret) { if let (certificateData, name) = try? await certificateHandler.keyBlobAndName(for: secret) {

View File

@ -78,7 +78,6 @@ extension SocketController {
provenance = SigningRequestTracer().provenance(from: fileHandle) provenance = SigningRequestTracer().provenance(from: fileHandle)
(messages, messagesContinuation) = AsyncStream.makeStream() (messages, messagesContinuation) = AsyncStream.makeStream()
Task { [messagesContinuation, logger] in Task { [messagesContinuation, logger] in
await fileHandle.waitForDataInBackgroundAndNotifyOnMainActor()
for await _ in NotificationCenter.default.notifications(named: .NSFileHandleDataAvailable, object: fileHandle) { for await _ in NotificationCenter.default.notifications(named: .NSFileHandleDataAvailable, object: fileHandle) {
let data = fileHandle.availableData let data = fileHandle.availableData
guard !data.isEmpty else { guard !data.isEmpty else {
@ -91,6 +90,9 @@ extension SocketController {
logger.debug("Socket controller yielded data.") logger.debug("Socket controller yielded data.")
} }
} }
Task {
await fileHandle.waitForDataInBackgroundAndNotifyOnMainActor()
}
} }
/// Writes new data to the socket. /// Writes new data to the socket.

View File

@ -31,18 +31,7 @@ public struct OpenSSHPublicKeyWriter: Sendable {
/// Generates an OpenSSH string representation of the secret. /// Generates an OpenSSH string representation of the secret.
/// - Returns: OpenSSH string representation of the secret. /// - Returns: OpenSSH string representation of the secret.
public func openSSHString<SecretType: Secret>(secret: SecretType) -> String { public func openSSHString<SecretType: Secret>(secret: SecretType) -> String {
let resolvedComment: String return [openSSHIdentifier(for: secret.keyType), data(secret: secret).base64EncodedString(), comment(secret: secret)]
if let comment = secret.publicKeyAttribution {
resolvedComment = comment
} else {
let dashedKeyName = secret.name.replacingOccurrences(of: " ", with: "-")
let dashedHostName = ["secretive", Host.current().localizedName, "local"]
.compactMap { $0 }
.joined(separator: ".")
.replacingOccurrences(of: " ", with: "-")
resolvedComment = "\(dashedKeyName)@\(dashedHostName)"
}
return [openSSHIdentifier(for: secret.keyType), data(secret: secret).base64EncodedString(), resolvedComment]
.compactMap { $0 } .compactMap { $0 }
.joined(separator: " ") .joined(separator: " ")
} }
@ -65,6 +54,19 @@ public struct OpenSSHPublicKeyWriter: Sendable {
.joined(separator: ":") .joined(separator: ":")
} }
public func comment<SecretType: Secret>(secret: SecretType) -> String {
if let comment = secret.publicKeyAttribution {
return comment
} else {
let dashedKeyName = secret.name.replacingOccurrences(of: " ", with: "-")
let dashedHostName = ["secretive", Host.current().localizedName, "local"]
.compactMap { $0 }
.joined(separator: ".")
.replacingOccurrences(of: " ", with: "-")
return "\(dashedKeyName)@\(dashedHostName)"
}
}
} }
extension OpenSSHPublicKeyWriter { extension OpenSSHPublicKeyWriter {

View File

@ -26,7 +26,8 @@ public final class PublicKeyFileStoreController: Sendable {
let untracked = Set(fullPathContents) let untracked = Set(fullPathContents)
.subtracting(validPaths) .subtracting(validPaths)
for path in untracked { for path in untracked {
try? FileManager.default.removeItem(at: URL(fileURLWithPath: path)) // string instead of fileURLWithPath since we're already using fileURL format.
try? FileManager.default.removeItem(at: URL(string: path)!)
} }
} }
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false, attributes: nil) try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false, attributes: nil)

View File

@ -26,7 +26,7 @@ extension SecureEnclave {
for await note in DistributedNotificationCenter.default().notifications(named: .secretStoreUpdated) { for await note in DistributedNotificationCenter.default().notifications(named: .secretStoreUpdated) {
guard Constants.notificationToken != (note.object as? String) else { guard Constants.notificationToken != (note.object as? String) else {
// Don't reload if we're the ones triggering this by reloading. // Don't reload if we're the ones triggering this by reloading.
return continue
} }
reloadSecrets() reloadSecrets()
} }
@ -112,7 +112,7 @@ extension SecureEnclave {
var accessError: SecurityError? var accessError: SecurityError?
let flags: SecAccessControlCreateFlags = switch attributes.authentication { let flags: SecAccessControlCreateFlags = switch attributes.authentication {
case .notRequired: case .notRequired:
[] [.privateKeyUsage]
case .presenceRequired: case .presenceRequired:
[.userPresence, .privateKeyUsage] [.userPresence, .privateKeyUsage]
case .biometryCurrent: case .biometryCurrent:

View File

@ -54,9 +54,7 @@ struct EditSecretView<StoreType: SecretStoreModifiable>: View {
func rename() { func rename() {
var attributes = secret.attributes var attributes = secret.attributes
if !publicKeyAttribution.isEmpty { attributes.publicKeyAttribution = publicKeyAttribution.isEmpty ? nil : publicKeyAttribution
attributes.publicKeyAttribution = publicKeyAttribution
}
Task { Task {
do { do {
try await store.update(secret: secret, name: name, attributes: attributes) try await store.update(secret: secret, name: name, attributes: attributes)