Skip to content

Commit 0872d6f

Browse files
authored
Registry login interactive mode cannot read password/token over 128 chars long (#6562) (#6614)
* Registry login interactive mode cannot read password/token over 128 chars long Motivation: `swift package-registry login` in interactive mode truncates input when the provided password/token is more than 128 chars long. This is a limitation of `getpass`. rdar://109372320 Modifications: - Use `readpassphrase`, which allows custom input length, instead of `getpass`. - Improve error message when the provided password/token is too long. - Increase password/token length limit to 512 chars * Continue using getpass on non-Darwin, non-Windows platforms * include more details in Windows error messages
1 parent 770fc45 commit 0872d6f

File tree

2 files changed

+47
-8
lines changed

2 files changed

+47
-8
lines changed

Sources/PackageRegistryTool/PackageRegistryTool+Auth.swift

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,26 @@ import TSCBasic
2222
#if os(Windows)
2323
import WinSDK
2424

25-
private func getpass(_ prompt: String) -> UnsafePointer<CChar> {
25+
private func readpassword(_ prompt: String) throws -> String {
2626
enum StaticStorage {
2727
static var buffer: UnsafeMutableBufferPointer<CChar> =
28-
.allocate(capacity: 255)
28+
.allocate(capacity: SwiftPackageRegistryTool.Login.passwordBufferSize)
2929
}
3030

3131
let hStdIn: HANDLE = GetStdHandle(STD_INPUT_HANDLE)
3232
if hStdIn == INVALID_HANDLE_VALUE {
33-
return UnsafePointer<CChar>(StaticStorage.buffer.baseAddress!)
33+
throw StringError("unable to read input: GetStdHandle returns INVALID_HANDLE_VALUE")
3434
}
3535

3636
var dwMode: DWORD = 0
3737
guard GetConsoleMode(hStdIn, &dwMode) else {
38-
return UnsafePointer<CChar>(StaticStorage.buffer.baseAddress!)
38+
throw StringError("unable to read input: GetConsoleMode failed")
3939
}
4040

4141
print(prompt, terminator: "")
4242

4343
guard SetConsoleMode(hStdIn, DWORD(ENABLE_LINE_INPUT)) else {
44-
return UnsafePointer<CChar>(StaticStorage.buffer.baseAddress!)
44+
throw StringError("unable to read input: SetConsoleMode failed")
4545
}
4646
defer { SetConsoleMode(hStdIn, dwMode) }
4747

@@ -53,7 +53,37 @@ private func getpass(_ prompt: String) -> UnsafePointer<CChar> {
5353
&dwNumberOfCharsRead,
5454
nil
5555
)
56-
return UnsafePointer<CChar>(StaticStorage.buffer.baseAddress!)
56+
57+
let password = String(cString: UnsafePointer<CChar>(StaticStorage.buffer.baseAddress!))
58+
guard password.count <= SwiftPackageRegistryTool.Login.maxPasswordLength else {
59+
throw SwiftPackageRegistryTool.ValidationError
60+
.credentialLengthLimitExceeded(SwiftPackageRegistryTool.Login.maxPasswordLength)
61+
}
62+
return password
63+
}
64+
#else
65+
private func readpassword(_ prompt: String) throws -> String {
66+
let password: String
67+
68+
#if canImport(Darwin)
69+
var buffer = [CChar](repeating: 0, count: SwiftPackageRegistryTool.Login.passwordBufferSize)
70+
71+
guard let passwordPtr = readpassphrase(prompt, &buffer, buffer.count, 0) else {
72+
throw StringError("unable to read input")
73+
}
74+
75+
password = String(cString: passwordPtr)
76+
#else
77+
// GNU C implementation of getpass has no limit on the password length
78+
// (https://man7.org/linux/man-pages/man3/getpass.3.html)
79+
password = String(cString: getpass(prompt))
80+
#endif
81+
82+
guard password.count <= SwiftPackageRegistryTool.Login.maxPasswordLength else {
83+
throw SwiftPackageRegistryTool.ValidationError
84+
.credentialLengthLimitExceeded(SwiftPackageRegistryTool.Login.maxPasswordLength)
85+
}
86+
return password
5787
}
5888
#endif
5989

@@ -63,6 +93,12 @@ extension SwiftPackageRegistryTool {
6393
abstract: "Log in to a registry"
6494
)
6595

96+
static let maxPasswordLength = 512
97+
// Define a larger buffer size so we read more than allowed, and
98+
// this way we can tell if the entered password is over the length
99+
// limit. One space is for \0, another is for the "overflowing" char.
100+
static let passwordBufferSize = Self.maxPasswordLength + 2
101+
66102
@OptionGroup(visibility: .hidden)
67103
var globalOptions: GlobalOptions
68104

@@ -140,7 +176,7 @@ extension SwiftPackageRegistryTool {
140176
saveChanges = false
141177
} else {
142178
// Prompt user for password
143-
storePassword = String(cString: getpass("Enter password for '\(storeUsername)': "))
179+
storePassword = try readpassword("Enter password for '\(storeUsername)': ")
144180
}
145181
} else {
146182
authenticationType = .token
@@ -162,7 +198,7 @@ extension SwiftPackageRegistryTool {
162198
saveChanges = false
163199
} else {
164200
// Prompt user for token
165-
storePassword = String(cString: getpass("Enter access token: "))
201+
storePassword = try readpassword("Enter access token: ")
166202
}
167203
}
168204

Sources/PackageRegistryTool/PackageRegistryTool.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ public struct SwiftPackageRegistryTool: ParsableCommand {
137137
case unknownRegistry
138138
case unknownCredentialStore
139139
case invalidCredentialStore(Error)
140+
case credentialLengthLimitExceeded(Int)
140141
}
141142

142143
static func getRegistriesConfig(_ swiftTool: SwiftTool) throws -> Workspace.Configuration.Registries {
@@ -181,6 +182,8 @@ extension SwiftPackageRegistryTool.ValidationError: CustomStringConvertible {
181182
return "no credential store available"
182183
case .invalidCredentialStore(let error):
183184
return "credential store is invalid: \(error.interpolationDescription)"
185+
case .credentialLengthLimitExceeded(let limit):
186+
return "password or access token must be \(limit) characters or less"
184187
}
185188
}
186189
}

0 commit comments

Comments
 (0)