Skip to content

Commit ff4f55d

Browse files
authored
Add bcrypt and OTP traits (#77)
* Add bcrypt and OTP traits * Tidy ups * Drop Swift version because Xcode * Fix lint issue * Fix version in README * Numptie * Migrate to using FoundationEssentials based API * Add mawr type safety * Remove last unsafe usage
1 parent b9eabc8 commit ff4f55d

File tree

12 files changed

+75
-30
lines changed

12 files changed

+75
-30
lines changed

Package.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
// swift-tools-version:6.2.3
1+
// swift-tools-version:6.2
2+
// This should really be 6.2.3, but Xcode yet again has a bug
23
import PackageDescription
34

45
let extraSettings: [SwiftSetting] = [
@@ -29,6 +30,14 @@ let package = Package(
2930
products: [
3031
.library(name: "Authentication", targets: ["Authentication"])
3132
],
33+
traits: [
34+
.trait(name: "bcrypt"),
35+
.trait(name: "OTP"),
36+
.default(enabledTraits: [
37+
"bcrypt",
38+
"OTP",
39+
]),
40+
],
3241
dependencies: [
3342
.package(url: "https://github.com/apple/swift-crypto.git", from: "4.0.0")
3443
],
@@ -43,8 +52,8 @@ let package = Package(
4352
.target(
4453
name: "Authentication",
4554
dependencies: [
46-
.target(name: "CVaporAuthBcrypt"),
47-
.product(name: "Crypto", package: "swift-crypto"),
55+
.target(name: "CVaporAuthBcrypt", condition: .when(traits: ["bcrypt"])),
56+
.product(name: "Crypto", package: "swift-crypto", condition: .when(traits: ["bcrypt", "OTP"])),
4857
],
4958
swiftSettings: extraSettings
5059
),

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Add the Authentication package to your `Package.swift` dependencies:
1818

1919
```swift
2020
dependencies: [
21-
.package(url: "https://github.com/vapor/authentication.git", from: "4.0.0")
21+
.package(url: "https://github.com/vapor/authentication.git", from: "3.0.0")
2222
]
2323
```
2424

File renamed without changes.

Sources/Authentication/OTP/OTP.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#if OTP
12
public import Crypto
23

34
#if canImport(FoundationEssentials)
@@ -285,3 +286,4 @@ extension FixedWidthInteger {
285286
return unsafe .init(bytes: &copy, count: MemoryLayout<Self>.size)
286287
}
287288
}
289+
#endif

Sources/Authentication/Passwords/Bcrypt/BcryptError.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#if bcrypt
12
@nonexhaustive
23
public enum BcryptError: Swift.Error, CustomStringConvertible, Sendable {
34
case invalidCost
@@ -29,3 +30,4 @@ public enum BcryptError: Swift.Error, CustomStringConvertible, Sendable {
2930
}
3031
}
3132
}
33+
#endif

Sources/Authentication/Passwords/Bcrypt/BcryptHasher.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#if bcrypt
12
#if canImport(FoundationEssentials)
23
public import FoundationEssentials
34
#else
@@ -30,3 +31,4 @@ public struct BcryptHasher: PasswordHasher {
3031
)
3132
}
3233
}
34+
#endif

Sources/Authentication/Passwords/Bcrypt/VaporBcrypt.swift

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
#if bcrypt
12
internal import CVaporAuthBcrypt
23

34
#if canImport(FoundationEssentials)
4-
internal import Foundation
5+
internal import FoundationEssentials
56
#else
67
internal import Foundation
78
#endif
@@ -82,11 +83,13 @@ public enum VaporBcrypt: Sendable {
8283

8384
var hashData = [CChar](repeating: 0, count: 128)
8485
var hashDataSpan = hashData.mutableSpan
85-
let result = unsafe vapor_auth_bcrypt_hashpass(plaintext, normalizedSalt, &hashDataSpan)
86+
let result = vapor_auth_bcrypt_hashpass(plaintext.utf8CString.span, normalizedSalt.utf8CString.span, &hashDataSpan)
8687
guard result == 0 else {
8788
throw BcryptError.hashFailure
8889
}
89-
guard let string = String(utf8String: hashData) else {
90+
// Remove null terminated characters
91+
let cleanedHashData = Array(hashData.prefix { $0 != 0 })
92+
guard let string = String(validating: cleanedHashData, as: UTF8.self) else {
9093
throw BcryptError.internalError
9194
}
9295

@@ -177,8 +180,11 @@ public enum VaporBcrypt: Sendable {
177180
/// - returns: Base 64 encoded plaintext
178181
private static func base64Encode(_ data: [UInt8]) throws(BcryptError) -> String {
179182
var encodedStringBytes = [CChar](repeating: 0, count: 25)
180-
let result = unsafe vapor_auth_encode_base64(&encodedStringBytes, data.span)
181-
guard result == 0, let encodedString = String(utf8String: encodedStringBytes) else {
183+
var span = encodedStringBytes.mutableSpan
184+
let result = vapor_auth_encode_base64(&span, data.span)
185+
// Remove null terminated characters
186+
let cleanedString = Array(encodedStringBytes.prefix { $0 != 0 })
187+
guard result == 0, let encodedString = String(validating: cleanedString, as: UTF8.self) else {
182188
throw BcryptError.internalError
183189
}
184190
return encodedString
@@ -224,3 +230,4 @@ extension Array where Element == UInt8 {
224230
return array
225231
}
226232
}
233+
#endif

Sources/CVaporAuthBcrypt/bcrypt.c

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,12 @@ static int decode_base64(u_int8_t *, size_t, const char *);
5050
* the core bcrypt function
5151
*/
5252
int
53-
vapor_auth_bcrypt_hashpass(
54-
const char *key,
55-
const char *salt,
56-
char *__counted_by(encryptedlen) encrypted __noescape,
53+
vapor_auth_bcrypt_hashpass(const char *_Nonnull __counted_by(keysize) key __noescape,
54+
const char *_Nonnull __counted_by(saltsize) salt __noescape,
55+
char *_Nonnull __counted_by(encryptedlen)
56+
encrypted __noescape,
57+
size_t keysize,
58+
size_t saltsize,
5759
size_t encryptedlen)
5860
{
5961
blf_ctx state;
@@ -76,6 +78,10 @@ vapor_auth_bcrypt_hashpass(
7678
if (salt[0] != BCRYPT_VERSION)
7779
goto inval;
7880

81+
/* keysize is the size of the key including a null terminator */
82+
if ((strlen(key) + 1) != keysize)
83+
goto inval;
84+
7985
/* Check for minor versions */
8086
switch ((minor = salt[1])) {
8187
case 'a':
@@ -149,8 +155,8 @@ vapor_auth_bcrypt_hashpass(
149155

150156

151157
snprintf(encrypted, 8, "$2%c$%2.2u$", minor, logr);
152-
vapor_auth_encode_base64(encrypted + 7, csalt, BCRYPT_MAXSALT);
153-
vapor_auth_encode_base64(encrypted + 7 + 22, ciphertext, 4 * BCRYPT_WORDS - 1);
158+
vapor_auth_encode_base64(encrypted + 7, csalt, encryptedlen, BCRYPT_MAXSALT);
159+
vapor_auth_encode_base64(encrypted + 7 + 22, ciphertext, encryptedlen, 4 * BCRYPT_WORDS - 1);
154160
explicit_bzero(&state, sizeof(state));
155161
explicit_bzero(ciphertext, sizeof(ciphertext));
156162
explicit_bzero(csalt, sizeof(csalt));
@@ -232,7 +238,10 @@ decode_base64(u_int8_t *buffer, size_t len, const char *b64data)
232238
* This works without = padding.
233239
*/
234240
int
235-
vapor_auth_encode_base64(char *b64buffer, const u_int8_t *__counted_by(len)data __noescape, size_t len)
241+
vapor_auth_encode_base64(char *_Nonnull __counted_by(bufferlen) b64buffer __noescape,
242+
const u_int8_t *_Nonnull __counted_by(len) data __noescape,
243+
size_t bufferlen,
244+
size_t len)
236245
{
237246
u_int8_t *bp = (u_int8_t *)b64buffer;
238247
const u_int8_t *p = data;

Sources/CVaporAuthBcrypt/bcrypt.h

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
#include <sys/types.h>
2-
#include <string.h>
3-
#include <stdio.h>
41
#include <lifetimebound.h>
52
#include <ptrcheck.h>
3+
#include <stdio.h>
4+
#include <string.h>
5+
#include <sys/types.h>
66

77
#if defined(_WIN32)
88
typedef unsigned char uint8_t;
@@ -19,23 +19,31 @@ typedef uint64_t u_int64_t;
1919
#include <stdint.h>
2020
#endif
2121

22-
#define explicit_bzero(s,n) memset(s, 0, n)
22+
#define explicit_bzero(s, n) memset(s, 0, n)
2323
#define DEF_WEAK(f)
2424

25-
2625
/* This implementation is adaptable to current computing power.
2726
* You can have up to 2^31 rounds which should be enough for some
2827
* time to come.
2928
*/
3029

3130
#define BCRYPT_VERSION '2'
32-
#define BCRYPT_MAXSALT 16 /* Precomputation is just so nice */
31+
#define BCRYPT_MAXSALT 16 /* Precomputation is just so nice */
3332
#define BCRYPT_WORDS 6 /* Ciphertext words */
34-
#define BCRYPT_MINLOGROUNDS 4 /* we have log2(rounds) in salt */
35-
36-
#define BCRYPT_SALTSPACE (7 + (BCRYPT_MAXSALT * 4 + 2) / 3 + 1)
37-
#define BCRYPT_HASHSPACE 61
38-
39-
40-
int vapor_auth_bcrypt_hashpass(const char *key, const char *salt, char *__counted_by(encryptedlen) encrypted __noescape, size_t encryptedlen);
41-
int vapor_auth_encode_base64(char *b64buffer, const u_int8_t *__counted_by(len)data __noescape, size_t len);
33+
#define BCRYPT_MINLOGROUNDS 4 /* we have log2(rounds) in salt */
34+
35+
#define BCRYPT_SALTSPACE (7 + (BCRYPT_MAXSALT * 4 + 2) / 3 + 1)
36+
#define BCRYPT_HASHSPACE 61
37+
38+
int vapor_auth_bcrypt_hashpass(const char *_Nonnull __counted_by(keysize) key __noescape,
39+
const char *_Nonnull __counted_by(saltsize) salt __noescape,
40+
char *_Nonnull __counted_by(encryptedlen)
41+
encrypted __noescape,
42+
size_t keysize,
43+
size_t saltsize,
44+
size_t encryptedlen);
45+
int vapor_auth_encode_base64(char *_Nonnull __counted_by(bufferlen) b64buffer __noescape,
46+
const u_int8_t *_Nonnull __counted_by(len)
47+
data __noescape,
48+
size_t bufferlen,
49+
size_t len);

Tests/AuthenticationTests/BcryptTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#if bcrypt
12
import Authentication
23
import Testing
34

@@ -66,3 +67,4 @@ struct BcryptTests {
6667
#expect(result)
6768
}
6869
}
70+
#endif

0 commit comments

Comments
 (0)