mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-09-19 14:48:01 +02:00
WIP
This commit is contained in:
@@ -3879,6 +3879,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"All" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Allow Connection Operations" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Allow Forwarding" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Allow Signing Operations" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Allowed Domains" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"app_menu_help_button" : {
|
"app_menu_help_button" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
@@ -12333,6 +12348,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"example.com" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"export SSH_AUTH_SOCK=%@" : {
|
"export SSH_AUTH_SOCK=%@" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -19205,6 +19223,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Key Properties" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"no_secure_storage_description" : {
|
"no_secure_storage_description" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
@@ -20154,6 +20175,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Restrictions" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"reveal_in_finder_button" : {
|
"reveal_in_finder_button" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
@@ -24973,6 +24997,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Specific" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"unnamed_secret" : {
|
"unnamed_secret" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ extension Agent {
|
|||||||
response = try await MainActor.run {
|
response = try await MainActor.run {
|
||||||
guard sessionID == nil else {
|
guard sessionID == nil else {
|
||||||
logger.error("Agent received bind request, but already bound.")
|
logger.error("Agent received bind request, but already bound.")
|
||||||
|
// FIXME: This will break forwarding for now.
|
||||||
throw BindingFailure()
|
throw BindingFailure()
|
||||||
}
|
}
|
||||||
logger.debug("Agent bound")
|
logger.debug("Agent bound")
|
||||||
@@ -147,6 +148,8 @@ extension Agent {
|
|||||||
throw NoMatchingKeyError()
|
throw NoMatchingKeyError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try evaluateRestrictions(secret.attributes.restrictions, from: provenance, for: target)
|
||||||
|
|
||||||
try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance, target: target)
|
try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance, target: target)
|
||||||
|
|
||||||
let rawRepresentation = try await store.sign(data: data, with: secret, for: provenance, target: target)
|
let rawRepresentation = try await store.sign(data: data, with: secret, for: provenance, target: target)
|
||||||
@@ -161,6 +164,22 @@ extension Agent {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Agent {
|
||||||
|
|
||||||
|
func evaluateRestrictions(_ restrictions: Restrictions?, from provenance: SigningRequestProvenance, for target: SigningRequestTarget?) throws(RestrictionError) {
|
||||||
|
guard let restrictions else { return }
|
||||||
|
print(restrictions)
|
||||||
|
throw .connectionsNotPermitted
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RestrictionError: Error {
|
||||||
|
case signingNotPermitted
|
||||||
|
case connectionsNotPermitted
|
||||||
|
case hostNotAllowed(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
extension Agent {
|
extension Agent {
|
||||||
|
|
||||||
/// Gives any store with no loaded secrets a chance to reload.
|
/// Gives any store with no loaded secrets a chance to reload.
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ public struct Attributes: Sendable, Codable, Hashable {
|
|||||||
|
|
||||||
/// The authentication requirements for the key. This is simply a description of the option recorded at creation – modifying it doers not modify the key's authentication requirements.
|
/// The authentication requirements for the key. This is simply a description of the option recorded at creation – modifying it doers not modify the key's authentication requirements.
|
||||||
public let authentication: AuthenticationRequirement
|
public let authentication: AuthenticationRequirement
|
||||||
|
|
||||||
|
/// The authentication restrictions for the key.
|
||||||
|
public var restrictions: Restrictions?
|
||||||
|
|
||||||
/// The string appended to the end of the SSH Public Key.
|
/// The string appended to the end of the SSH Public Key.
|
||||||
/// If nil, a default value will be used.
|
/// If nil, a default value will be used.
|
||||||
public var publicKeyAttribution: String?
|
public var publicKeyAttribution: String?
|
||||||
@@ -15,10 +18,12 @@ public struct Attributes: Sendable, Codable, Hashable {
|
|||||||
public init(
|
public init(
|
||||||
keyType: KeyType,
|
keyType: KeyType,
|
||||||
authentication: AuthenticationRequirement,
|
authentication: AuthenticationRequirement,
|
||||||
|
restrictions: Restrictions?,
|
||||||
publicKeyAttribution: String? = nil
|
publicKeyAttribution: String? = nil
|
||||||
) {
|
) {
|
||||||
self.keyType = keyType
|
self.keyType = keyType
|
||||||
self.authentication = authentication
|
self.authentication = authentication
|
||||||
|
self.restrictions = restrictions
|
||||||
self.publicKeyAttribution = publicKeyAttribution
|
self.publicKeyAttribution = publicKeyAttribution
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,17 +38,17 @@ public enum AuthenticationRequirement: String, Hashable, Sendable, Codable, Iden
|
|||||||
|
|
||||||
/// Authentication is not required for usage.
|
/// Authentication is not required for usage.
|
||||||
case notRequired
|
case notRequired
|
||||||
|
|
||||||
/// The user needs to authenticate, using either a biometric option, a connected authorized watch, or password entry..
|
/// The user needs to authenticate, using either a biometric option, a connected authorized watch, or password entry..
|
||||||
case presenceRequired
|
case presenceRequired
|
||||||
|
|
||||||
/// ONLY the current set of biometric data, as matching at time of creation, is accepted.
|
/// ONLY the current set of biometric data, as matching at time of creation, is accepted.
|
||||||
/// - Warning: This is a dangerous option prone to data loss. The user should be warned before configuring this key that if they modify their enrolled biometry INCLUDING by simply adding a new entry (ie, adding another fingeprting), the key will no longer be able to be accessed. This cannot be overridden with a password.
|
/// - Warning: This is a dangerous option prone to data loss. The user should be warned before configuring this key that if they modify their enrolled biometry INCLUDING by simply adding a new entry (ie, adding another fingeprting), the key will no longer be able to be accessed. This cannot be overridden with a password.
|
||||||
case biometryCurrent
|
case biometryCurrent
|
||||||
|
|
||||||
/// The authentication requirement was not recorded at creation, and is unknown.
|
/// The authentication requirement was not recorded at creation, and is unknown.
|
||||||
case unknown
|
case unknown
|
||||||
|
|
||||||
/// Whether or not the key is known to require authentication.
|
/// Whether or not the key is known to require authentication.
|
||||||
public var required: Bool {
|
public var required: Bool {
|
||||||
self == .presenceRequired || self == .biometryCurrent
|
self == .presenceRequired || self == .biometryCurrent
|
||||||
@@ -53,3 +58,46 @@ public enum AuthenticationRequirement: String, Hashable, Sendable, Codable, Iden
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The restrictions for the key.
|
||||||
|
public struct Restrictions: Hashable, Sendable, Codable, Identifiable {
|
||||||
|
|
||||||
|
public enum AllowedDomains: Hashable, Sendable, Codable {
|
||||||
|
case all
|
||||||
|
case specific([String])
|
||||||
|
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
switch self {
|
||||||
|
case .all:
|
||||||
|
break
|
||||||
|
case .specific(let values):
|
||||||
|
values.hash(into: &hasher)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AllowedProvenancePaths: Hashable, Sendable, Codable, Identifiable {
|
||||||
|
case all
|
||||||
|
case specific([String])
|
||||||
|
|
||||||
|
public var id: Int {
|
||||||
|
hashValue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public var allowForwarding: Bool
|
||||||
|
public var allowSigning: Bool
|
||||||
|
public var allowConnections: Bool
|
||||||
|
public var allowedDomains: AllowedDomains
|
||||||
|
public var allowedProvenancePaths: AllowedProvenancePaths
|
||||||
|
|
||||||
|
public var id: Restrictions {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static let `default` = Restrictions(allowForwarding: true, allowSigning: true, allowConnections: true, allowedDomains: .all, allowedProvenancePaths: .all)
|
||||||
|
public static let `default` = Restrictions(allowForwarding: true, allowSigning: true, allowConnections: true, allowedDomains: .specific(["example.com"]), allowedProvenancePaths: .all)
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ extension SecureEnclave {
|
|||||||
.contains("DeviceOwnerAuthentication") ? .presenceRequired : .unknown
|
.contains("DeviceOwnerAuthentication") ? .presenceRequired : .unknown
|
||||||
do {
|
do {
|
||||||
let parsed = try CryptoKit.SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: tokenObjectID)
|
let parsed = try CryptoKit.SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: tokenObjectID)
|
||||||
let secret = Secret(id: UUID().uuidString, name: name, publicKey: parsed.publicKey.x963Representation, attributes: Attributes(keyType: .init(algorithm: .ecdsa, size: 256), authentication: auth))
|
let secret = Secret(id: UUID().uuidString, name: name, publicKey: parsed.publicKey.x963Representation, attributes: Attributes(keyType: .init(algorithm: .ecdsa, size: 256), authentication: auth, restrictions: .default))
|
||||||
guard !migratedPublicKeys.contains(parsed.publicKey.x963Representation) else {
|
guard !migratedPublicKeys.contains(parsed.publicKey.x963Representation) else {
|
||||||
logger.log("Skipping \(name), public key already present. Marking as migrated.")
|
logger.log("Skipping \(name), public key already present. Marking as migrated.")
|
||||||
markMigrated(secret: secret, oldID: id)
|
markMigrated(secret: secret, oldID: id)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ extension SmartCard {
|
|||||||
public let name: String
|
public let name: String
|
||||||
public let publicKey: Data
|
public let publicKey: Data
|
||||||
public var attributes: Attributes
|
public var attributes: Attributes
|
||||||
|
public var restrictions: Restrictions?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ extension SmartCard.Store {
|
|||||||
let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)!
|
let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)!
|
||||||
let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any]
|
let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any]
|
||||||
let publicKey = publicKeyAttributes[kSecValueData] as! Data
|
let publicKey = publicKeyAttributes[kSecValueData] as! Data
|
||||||
let attributes = Attributes(keyType: KeyType(secAttr: algorithmSecAttr, size: keySize)!, authentication: .presenceRequired)
|
let attributes = Attributes(keyType: KeyType(secAttr: algorithmSecAttr, size: keySize)!, authentication: .presenceRequired, restrictions: .default)
|
||||||
let secret = SmartCard.Secret(id: tokenID, name: name, publicKey: publicKey, attributes: attributes)
|
let secret = SmartCard.Secret(id: tokenID, name: name, publicKey: publicKey, attributes: attributes)
|
||||||
guard signatureAlgorithm(for: secret) != nil else { return nil }
|
guard signatureAlgorithm(for: secret) != nil else { return nil }
|
||||||
return secret
|
return secret
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ extension Preview {
|
|||||||
Attributes(
|
Attributes(
|
||||||
keyType: .init(algorithm: .ecdsa, size: 256),
|
keyType: .init(algorithm: .ecdsa, size: 256),
|
||||||
authentication: .presenceRequired,
|
authentication: .presenceRequired,
|
||||||
|
restrictions: .default
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
@State private var name = ""
|
@State private var name = ""
|
||||||
@State private var keyAttribution = ""
|
@State private var keyAttribution = ""
|
||||||
@State private var authenticationRequirement: AuthenticationRequirement = .presenceRequired
|
@State private var authenticationRequirement: AuthenticationRequirement = .presenceRequired
|
||||||
|
@State private var restrictions: Restrictions = .default
|
||||||
@State private var keyType: KeyType?
|
@State private var keyType: KeyType?
|
||||||
@State var advanced = false
|
@State var advanced = true // FIXME: Set back
|
||||||
@State var errorText: String?
|
@State var errorText: String?
|
||||||
|
|
||||||
private var authenticationOptions: [AuthenticationRequirement] {
|
private var authenticationOptions: [AuthenticationRequirement] {
|
||||||
@@ -72,6 +73,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if advanced {
|
if advanced {
|
||||||
|
SecretRestrictionsView(restrictions: $restrictions)
|
||||||
Section {
|
Section {
|
||||||
VStack {
|
VStack {
|
||||||
Picker(.createSecretKeyTypeLabel, selection: $keyType) {
|
Picker(.createSecretKeyTypeLabel, selection: $keyType) {
|
||||||
@@ -107,6 +109,8 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Key Properties")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let errorText {
|
if let errorText {
|
||||||
@@ -146,6 +150,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
attributes: .init(
|
attributes: .init(
|
||||||
keyType: keyType!,
|
keyType: keyType!,
|
||||||
authentication: authenticationRequirement,
|
authentication: authenticationRequirement,
|
||||||
|
restrictions: restrictions,
|
||||||
publicKeyAttribution: attribution
|
publicKeyAttribution: attribution
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -158,7 +163,52 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
struct SecretRestrictionsView: View {
|
||||||
|
|
||||||
//#Preview {
|
@Binding var restrictions: Restrictions
|
||||||
// CreateSecretView(store: Preview.StoreModifiable()) { _ in }
|
|
||||||
//}
|
struct IdentifiedString: Identifiable {
|
||||||
|
let value: String
|
||||||
|
var id: String { value }
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Section {
|
||||||
|
Toggle("Allow Forwarding", isOn: $restrictions.allowForwarding)
|
||||||
|
Toggle("Allow Signing Operations", isOn: $restrictions.allowSigning)
|
||||||
|
Toggle("Allow Connection Operations", isOn: $restrictions.allowConnections)
|
||||||
|
Picker(selection: $restrictions.allowedDomains) {
|
||||||
|
Text("All")
|
||||||
|
.tag(Restrictions.AllowedDomains.all)
|
||||||
|
Text("Specific")
|
||||||
|
.tag(Restrictions.AllowedDomains.specific([]))
|
||||||
|
} label: {
|
||||||
|
Text("Allowed Domains")
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Restrictions")
|
||||||
|
}
|
||||||
|
if restrictions.allowedDomains != .all {
|
||||||
|
Section {
|
||||||
|
switch restrictions.allowedDomains {
|
||||||
|
case .all:
|
||||||
|
EmptyView()
|
||||||
|
case .specific(let array):
|
||||||
|
if restrictions.allowedDomains != .all {
|
||||||
|
ForEach((array + [""]).map({IdentifiedString(value: $0)})) {
|
||||||
|
TextField("", text: .constant($0.value), prompt: Text("example.com"))
|
||||||
|
.labelsHidden()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Allowed Domains")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
CreateSecretView(store: Preview.StoreModifiable()) { _ in }
|
||||||
|
.frame(height: 1000)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user