Skip to content

Commit

Permalink
Registry login interactive mode cannot read password/token over 128 c…
Browse files Browse the repository at this point in the history
…hars 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
  • Loading branch information
yim-lee committed May 26, 2023
1 parent 770fc45 commit 0872d6f
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 8 deletions.
52 changes: 44 additions & 8 deletions Sources/PackageRegistryTool/PackageRegistryTool+Auth.swift
Expand Up @@ -22,26 +22,26 @@ import TSCBasic
#if os(Windows)
import WinSDK

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

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

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

print(prompt, terminator: "")

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

Expand All @@ -53,7 +53,37 @@ private func getpass(_ prompt: String) -> UnsafePointer<CChar> {
&dwNumberOfCharsRead,
nil
)
return UnsafePointer<CChar>(StaticStorage.buffer.baseAddress!)

let password = String(cString: UnsafePointer<CChar>(StaticStorage.buffer.baseAddress!))
guard password.count <= SwiftPackageRegistryTool.Login.maxPasswordLength else {
throw SwiftPackageRegistryTool.ValidationError
.credentialLengthLimitExceeded(SwiftPackageRegistryTool.Login.maxPasswordLength)
}
return password
}
#else
private func readpassword(_ prompt: String) throws -> String {
let password: String

#if canImport(Darwin)
var buffer = [CChar](repeating: 0, count: SwiftPackageRegistryTool.Login.passwordBufferSize)

guard let passwordPtr = readpassphrase(prompt, &buffer, buffer.count, 0) else {
throw StringError("unable to read input")
}

password = String(cString: passwordPtr)
#else
// GNU C implementation of getpass has no limit on the password length
// (https://man7.org/linux/man-pages/man3/getpass.3.html)
password = String(cString: getpass(prompt))
#endif

guard password.count <= SwiftPackageRegistryTool.Login.maxPasswordLength else {
throw SwiftPackageRegistryTool.ValidationError
.credentialLengthLimitExceeded(SwiftPackageRegistryTool.Login.maxPasswordLength)
}
return password
}
#endif

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

static let maxPasswordLength = 512
// Define a larger buffer size so we read more than allowed, and
// this way we can tell if the entered password is over the length
// limit. One space is for \0, another is for the "overflowing" char.
static let passwordBufferSize = Self.maxPasswordLength + 2

@OptionGroup(visibility: .hidden)
var globalOptions: GlobalOptions

Expand Down Expand Up @@ -140,7 +176,7 @@ extension SwiftPackageRegistryTool {
saveChanges = false
} else {
// Prompt user for password
storePassword = String(cString: getpass("Enter password for '\(storeUsername)': "))
storePassword = try readpassword("Enter password for '\(storeUsername)': ")
}
} else {
authenticationType = .token
Expand All @@ -162,7 +198,7 @@ extension SwiftPackageRegistryTool {
saveChanges = false
} else {
// Prompt user for token
storePassword = String(cString: getpass("Enter access token: "))
storePassword = try readpassword("Enter access token: ")
}
}

Expand Down
3 changes: 3 additions & 0 deletions Sources/PackageRegistryTool/PackageRegistryTool.swift
Expand Up @@ -137,6 +137,7 @@ public struct SwiftPackageRegistryTool: ParsableCommand {
case unknownRegistry
case unknownCredentialStore
case invalidCredentialStore(Error)
case credentialLengthLimitExceeded(Int)
}

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

0 comments on commit 0872d6f

Please sign in to comment.