mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-04-10 03:07:22 +02:00
Compare commits
7 Commits
newsetup_l
...
extensions
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11f1f83113 | ||
|
|
3e128d2a81 | ||
|
|
935ac32ea2 | ||
|
|
a0a632f245 | ||
|
|
51fed9e593 | ||
|
|
f652d1d961 | ||
|
|
8aacd428b1 |
@@ -43,7 +43,7 @@ extension Agent {
|
|||||||
}
|
}
|
||||||
let requestTypeInt = data[4]
|
let requestTypeInt = data[4]
|
||||||
guard let requestType = SSHAgent.RequestType(rawValue: requestTypeInt) else {
|
guard let requestType = SSHAgent.RequestType(rawValue: requestTypeInt) else {
|
||||||
logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription)")
|
logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription) for unknown request type \(requestTypeInt)")
|
||||||
return SSHAgent.ResponseType.agentFailure.data.lengthAndData
|
return SSHAgent.ResponseType.agentFailure.data.lengthAndData
|
||||||
}
|
}
|
||||||
logger.debug("Agent handling request of type \(requestType.debugDescription)")
|
logger.debug("Agent handling request of type \(requestType.debugDescription)")
|
||||||
@@ -66,10 +66,25 @@ extension Agent {
|
|||||||
response.append(SSHAgent.ResponseType.agentSignResponse.data)
|
response.append(SSHAgent.ResponseType.agentSignResponse.data)
|
||||||
response.append(try await sign(data: data, provenance: provenance))
|
response.append(try await sign(data: data, provenance: provenance))
|
||||||
logger.debug("Agent returned \(SSHAgent.ResponseType.agentSignResponse.debugDescription)")
|
logger.debug("Agent returned \(SSHAgent.ResponseType.agentSignResponse.debugDescription)")
|
||||||
|
case .protocolExtension:
|
||||||
|
response.append(SSHAgent.ResponseType.agentExtensionResponse.data)
|
||||||
|
try await handleExtension(data)
|
||||||
|
default:
|
||||||
|
let reader = OpenSSHReader(data: data)
|
||||||
|
while true {
|
||||||
|
do {
|
||||||
|
let payloadHash = try reader.readNextChunk()
|
||||||
|
print(String(String(decoding: payloadHash, as: UTF8.self)))
|
||||||
|
print(payloadHash)
|
||||||
|
} catch {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug("Agent received valid request of type \(requestType.debugDescription), but not currently supported.")
|
||||||
|
response.append(SSHAgent.ResponseType.agentFailure.data)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
response.removeAll()
|
response = SSHAgent.ResponseType.agentFailure.data
|
||||||
response.append(SSHAgent.ResponseType.agentFailure.data)
|
|
||||||
logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription)")
|
logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription)")
|
||||||
}
|
}
|
||||||
return response.lengthAndData
|
return response.lengthAndData
|
||||||
@@ -77,6 +92,28 @@ extension Agent {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PROTOCOL EXTENSIONS
|
||||||
|
extension Agent {
|
||||||
|
|
||||||
|
func handleExtension(_ data: Data) async throws {
|
||||||
|
let reader = OpenSSHReader(data: data)
|
||||||
|
guard try reader.readNextChunkAsString() == "session-bind@openssh.com" else { throw UnsupportedExtensionError() }
|
||||||
|
let hostKey = try reader.readNextChunk()
|
||||||
|
let keyReader = OpenSSHReader(data: hostKey)
|
||||||
|
_ = try keyReader.readNextChunkAsString() // Key Type
|
||||||
|
let keyData = try keyReader.readNextChunk()
|
||||||
|
let sessionID = try reader.readNextChunk()
|
||||||
|
let signatureData = try reader.readNextChunk()
|
||||||
|
let forwarding = try reader.readNextBytes(as: Bool.self)
|
||||||
|
let signatureReader = OpenSSHSignatureReader()
|
||||||
|
guard try signatureReader.verify(signatureData, for: sessionID, with: keyData) else { throw SignatureVerificationFailedError() }
|
||||||
|
print("Fowarding: \(forwarding)")
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UnsupportedExtensionError: Error {}
|
||||||
|
struct SignatureVerificationFailedError: Error {}
|
||||||
|
}
|
||||||
|
|
||||||
extension Agent {
|
extension Agent {
|
||||||
|
|
||||||
/// Lists the identities available for signing operations
|
/// Lists the identities available for signing operations
|
||||||
@@ -89,9 +126,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) {
|
||||||
@@ -113,7 +149,7 @@ extension Agent {
|
|||||||
/// - Returns: An OpenSSH formatted Data payload containing the signed data response.
|
/// - Returns: An OpenSSH formatted Data payload containing the signed data response.
|
||||||
func sign(data: Data, provenance: SigningRequestProvenance) async throws -> Data {
|
func sign(data: Data, provenance: SigningRequestProvenance) async throws -> Data {
|
||||||
let reader = OpenSSHReader(data: data)
|
let reader = OpenSSHReader(data: data)
|
||||||
let payloadHash = reader.readNextChunk()
|
let payloadHash = try reader.readNextChunk()
|
||||||
let hash: Data
|
let hash: Data
|
||||||
|
|
||||||
// Check if hash is actually an openssh certificate and reconstruct the public key if it is
|
// Check if hash is actually an openssh certificate and reconstruct the public key if it is
|
||||||
@@ -130,7 +166,7 @@ extension Agent {
|
|||||||
|
|
||||||
try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance)
|
try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance)
|
||||||
|
|
||||||
let dataToSign = reader.readNextChunk()
|
let dataToSign = try reader.readNextChunk()
|
||||||
let rawRepresentation = try await store.sign(data: dataToSign, with: secret, for: provenance)
|
let rawRepresentation = try await store.sign(data: dataToSign, with: secret, for: provenance)
|
||||||
let signedData = signatureWriter.data(secret: secret, signature: rawRepresentation)
|
let signedData = signatureWriter.data(secret: secret, signature: rawRepresentation)
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,32 @@ extension SSHAgent {
|
|||||||
|
|
||||||
case requestIdentities = 11
|
case requestIdentities = 11
|
||||||
case signRequest = 13
|
case signRequest = 13
|
||||||
|
case addIdentity = 17
|
||||||
|
case removeIdentity = 18
|
||||||
|
case removeAllIdentities = 19
|
||||||
|
case addIDConstrained = 25
|
||||||
|
case addSmartcardKey = 20
|
||||||
|
case removeSmartcardKey = 21
|
||||||
|
case lock = 22
|
||||||
|
case unlock = 23
|
||||||
|
case addSmartcardKeyConstrained = 26
|
||||||
|
case protocolExtension = 27
|
||||||
|
|
||||||
|
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .requestIdentities:
|
case .requestIdentities: "SSH_AGENTC_REQUEST_IDENTITIES"
|
||||||
return "RequestIdentities"
|
case .signRequest: "SSH_AGENTC_SIGN_REQUEST"
|
||||||
case .signRequest:
|
case .addIdentity: "SSH_AGENTC_ADD_IDENTITY"
|
||||||
return "SignRequest"
|
case .removeIdentity: "SSH_AGENTC_REMOVE_IDENTITY"
|
||||||
|
case .removeAllIdentities: "SSH_AGENTC_REMOVE_ALL_IDENTITIES"
|
||||||
|
case .addIDConstrained: "SSH_AGENTC_ADD_ID_CONSTRAINED"
|
||||||
|
case .addSmartcardKey: "SSH_AGENTC_ADD_SMARTCARD_KEY"
|
||||||
|
case .removeSmartcardKey: "SSH_AGENTC_REMOVE_SMARTCARD_KEY"
|
||||||
|
case .lock: "SSH_AGENTC_LOCK"
|
||||||
|
case .unlock: "SSH_AGENTC_UNLOCK"
|
||||||
|
case .addSmartcardKeyConstrained: "SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED"
|
||||||
|
case .protocolExtension: "SSH_AGENTC_EXTENSION"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,17 +47,17 @@ extension SSHAgent {
|
|||||||
case agentSuccess = 6
|
case agentSuccess = 6
|
||||||
case agentIdentitiesAnswer = 12
|
case agentIdentitiesAnswer = 12
|
||||||
case agentSignResponse = 14
|
case agentSignResponse = 14
|
||||||
|
case agentExtensionFailure = 28
|
||||||
|
case agentExtensionResponse = 29
|
||||||
|
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .agentFailure:
|
case .agentFailure: "SSH_AGENT_FAILURE"
|
||||||
return "AgentFailure"
|
case .agentSuccess: "SSH_AGENT_SUCCESS"
|
||||||
case .agentSuccess:
|
case .agentIdentitiesAnswer: "SSH_AGENT_IDENTITIES_ANSWER"
|
||||||
return "AgentSuccess"
|
case .agentSignResponse: "SSH_AGENT_SIGN_RESPONSE"
|
||||||
case .agentIdentitiesAnswer:
|
case .agentExtensionFailure: "SSH_AGENT_EXTENSION_FAILURE"
|
||||||
return "AgentIdentitiesAnswer"
|
case .agentExtensionResponse: "SSH_AGENT_EXTENSION_RESPONSE"
|
||||||
case .agentSignResponse:
|
|
||||||
return "AgentSignResponse"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -30,14 +30,15 @@ public actor OpenSSHCertificateHandler: Sendable {
|
|||||||
/// - Returns: A ``Data`` object containing the public key in OpenSSH wire format if the ``Data`` is an OpenSSH certificate hash, otherwise nil.
|
/// - Returns: A ``Data`` object containing the public key in OpenSSH wire format if the ``Data`` is an OpenSSH certificate hash, otherwise nil.
|
||||||
public func publicKeyHash(from hash: Data) -> Data? {
|
public func publicKeyHash(from hash: Data) -> Data? {
|
||||||
let reader = OpenSSHReader(data: hash)
|
let reader = OpenSSHReader(data: hash)
|
||||||
let certType = String(decoding: reader.readNextChunk(), as: UTF8.self)
|
do {
|
||||||
|
let certType = String(decoding: try reader.readNextChunk(), as: UTF8.self)
|
||||||
switch certType {
|
switch certType {
|
||||||
case "ecdsa-sha2-nistp256-cert-v01@openssh.com",
|
case "ecdsa-sha2-nistp256-cert-v01@openssh.com",
|
||||||
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
|
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
|
||||||
"ecdsa-sha2-nistp521-cert-v01@openssh.com":
|
"ecdsa-sha2-nistp521-cert-v01@openssh.com":
|
||||||
_ = reader.readNextChunk() // nonce
|
_ = try reader.readNextChunk() // nonce
|
||||||
let curveIdentifier = reader.readNextChunk()
|
let curveIdentifier = try reader.readNextChunk()
|
||||||
let publicKey = reader.readNextChunk()
|
let publicKey = try reader.readNextChunk()
|
||||||
|
|
||||||
let openSSHIdentifier = certType.replacingOccurrences(of: "-cert-v01@openssh.com", with: "")
|
let openSSHIdentifier = certType.replacingOccurrences(of: "-cert-v01@openssh.com", with: "")
|
||||||
return openSSHIdentifier.lengthAndData +
|
return openSSHIdentifier.lengthAndData +
|
||||||
@@ -46,6 +47,9 @@ public actor OpenSSHCertificateHandler: Sendable {
|
|||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to find an OpenSSH Certificate that corresponds to a ``Secret``
|
/// Attempts to find an OpenSSH Certificate that corresponds to a ``Secret``
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -95,7 +97,7 @@ extension OpenSSHPublicKeyWriter {
|
|||||||
|
|
||||||
extension OpenSSHPublicKeyWriter {
|
extension OpenSSHPublicKeyWriter {
|
||||||
|
|
||||||
public func rsaPublicKeyBlob<SecretType: Secret>(secret: SecretType) -> Data {
|
func rsaPublicKeyBlob<SecretType: Secret>(secret: SecretType) -> Data {
|
||||||
// Cheap way to pull out e and n as defined in https://datatracker.ietf.org/doc/html/rfc4253
|
// Cheap way to pull out e and n as defined in https://datatracker.ietf.org/doc/html/rfc4253
|
||||||
// Keychain stores it as a thin ASN.1 wrapper with this format:
|
// Keychain stores it as a thin ASN.1 wrapper with this format:
|
||||||
// [4 byte prefix][2 byte prefix][n][2 byte prefix][e]
|
// [4 byte prefix][2 byte prefix][n][2 byte prefix][e]
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ public final class OpenSSHReader {
|
|||||||
|
|
||||||
/// Reads the next chunk of data from the playload.
|
/// Reads the next chunk of data from the playload.
|
||||||
/// - Returns: The next chunk of data.
|
/// - Returns: The next chunk of data.
|
||||||
public func readNextChunk() -> Data {
|
public func readNextChunk() throws -> Data {
|
||||||
|
guard remaining.count > UInt32.bitWidth/8 else { throw EndOfData() }
|
||||||
let lengthRange = 0..<(UInt32.bitWidth/8)
|
let lengthRange = 0..<(UInt32.bitWidth/8)
|
||||||
let lengthChunk = remaining[lengthRange]
|
let lengthChunk = remaining[lengthRange]
|
||||||
remaining.removeSubrange(lengthRange)
|
remaining.removeSubrange(lengthRange)
|
||||||
@@ -25,4 +26,18 @@ public final class OpenSSHReader {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func readNextBytes<T>(as: T.Type) throws -> T {
|
||||||
|
let lengthRange = 0..<MemoryLayout<T>.size
|
||||||
|
let lengthChunk = remaining[lengthRange]
|
||||||
|
remaining.removeSubrange(lengthRange)
|
||||||
|
return lengthChunk.bytes.unsafeLoad(as: T.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public func readNextChunkAsString() throws -> String {
|
||||||
|
try String(decoding: readNextChunk(), as: UTF8.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct EndOfData: Error {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import Foundation
|
||||||
|
import CryptoKit
|
||||||
|
import Security
|
||||||
|
|
||||||
|
/// Reads OpenSSH representations of Secrets.
|
||||||
|
public struct OpenSSHSignatureReader: Sendable {
|
||||||
|
|
||||||
|
/// Initializes the reader.
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func verify(_ signatureData: Data, for signedData: Data, with publicKey: Data) throws -> Bool {
|
||||||
|
let reader = OpenSSHReader(data: signatureData)
|
||||||
|
let signatureType = try reader.readNextChunkAsString()
|
||||||
|
let signatureData = try reader.readNextChunk()
|
||||||
|
switch signatureType {
|
||||||
|
case "ssh-rsa":
|
||||||
|
let attributes = KeychainDictionary([
|
||||||
|
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
||||||
|
kSecAttrKeySizeInBits: 2048,
|
||||||
|
kSecAttrKeyClass: kSecAttrKeyClassPublic
|
||||||
|
])
|
||||||
|
var verifyError: SecurityError?
|
||||||
|
let untyped: CFTypeRef? = SecKeyCreateWithData(publicKey as CFData, attributes, &verifyError)
|
||||||
|
guard let untypedSafe = untyped else {
|
||||||
|
throw KeychainError(statusCode: errSecSuccess)
|
||||||
|
}
|
||||||
|
let key = untypedSafe as! SecKey
|
||||||
|
return SecKeyVerifySignature(key, .rsaSignatureMessagePKCS1v15SHA512, signedData as CFData, signatureData as CFData, nil)
|
||||||
|
case "ecdsa-sha2-nistp256":
|
||||||
|
return try P256.Signing.PublicKey(rawRepresentation: publicKey).isValidSignature(.init(rawRepresentation: signatureData), for: signedData)
|
||||||
|
case "ecdsa-sha2-nistp384":
|
||||||
|
return try P384.Signing.PublicKey(rawRepresentation: publicKey).isValidSignature(.init(rawRepresentation: signatureData), for: signedData)
|
||||||
|
case "ecdsa-sha2-nistp521":
|
||||||
|
return try P521.Signing.PublicKey(rawRepresentation: publicKey).isValidSignature(.init(rawRepresentation: signatureData), for: signedData)
|
||||||
|
case "ssh-ed25519":
|
||||||
|
return try Curve25519.Signing.PublicKey(rawRepresentation: publicKey).isValidSignature(signatureData, for: signedData)
|
||||||
|
case "ssh-mldsa-65":
|
||||||
|
if #available(macOS 26.0, *) {
|
||||||
|
return try MLDSA65.PublicKey(rawRepresentation: publicKey).isValidSignature(signatureData, for: signedData)
|
||||||
|
} else {
|
||||||
|
throw UnsupportedSignatureType()
|
||||||
|
}
|
||||||
|
case "ssh-mldsa-87":
|
||||||
|
if #available(macOS 26.0, *) {
|
||||||
|
return try MLDSA87.PublicKey(rawRepresentation: publicKey).isValidSignature(signatureData, for: signedData)
|
||||||
|
} else {
|
||||||
|
throw UnsupportedSignatureType()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw UnsupportedSignatureType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct UnsupportedSignatureType: Error {}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -53,9 +53,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)
|
||||||
|
|||||||
Reference in New Issue
Block a user