diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/crc16.zig | 4 | ||||
| -rw-r--r-- | src/nkeys.zig | 172 | ||||
| -rw-r--r-- | src/znk.zig | 19 |
3 files changed, 88 insertions, 107 deletions
diff --git a/src/crc16.zig b/src/crc16.zig index b00c795..2c49500 100644 --- a/src/crc16.zig +++ b/src/crc16.zig | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | const Error = error{InvalidChecksum}; | 1 | pub const InvalidChecksumError = error{InvalidChecksum}; |
| 2 | 2 | ||
| 3 | const crc16tab: [256]u16 = tab: { | 3 | const crc16tab: [256]u16 = tab: { |
| 4 | @setEvalBranchQuota(10000); | 4 | @setEvalBranchQuota(10000); |
| @@ -36,6 +36,6 @@ pub fn make(data: []const u8) u16 { | |||
| 36 | } | 36 | } |
| 37 | 37 | ||
| 38 | // validate will check the calculated CRC16 checksum for data against the expected. | 38 | // validate will check the calculated CRC16 checksum for data against the expected. |
| 39 | pub fn validate(data: []const u8, expected: u16) !void { | 39 | pub fn validate(data: []const u8, expected: u16) InvalidChecksumError!void { |
| 40 | if (make(data) != expected) return error.InvalidChecksum; | 40 | if (make(data) != expected) return error.InvalidChecksum; |
| 41 | } | 41 | } |
diff --git a/src/nkeys.zig b/src/nkeys.zig index 1880fa8..e8410fd 100644 --- a/src/nkeys.zig +++ b/src/nkeys.zig | |||
| @@ -7,13 +7,11 @@ const Ed25519 = crypto.sign.Ed25519; | |||
| 7 | const mem = std.mem; | 7 | const mem = std.mem; |
| 8 | const testing = std.testing; | 8 | const testing = std.testing; |
| 9 | 9 | ||
| 10 | const Error = error{ | 10 | pub const InvalidPrefixByteError = error{InvalidPrefixByte}; |
| 11 | InvalidPrefixByte, | 11 | pub const InvalidEncodingError = error{InvalidEncoding}; |
| 12 | InvalidEncoding, | 12 | pub const InvalidSeedError = error{InvalidSeed}; |
| 13 | InvalidSeed, | 13 | pub const NoNkeySeedFoundError = error{NoNkeySeedFound}; |
| 14 | NoNkeySeedFound, | 14 | pub const NoNkeyUserSeedFoundError = error{NoNkeyUserSeedFound}; |
| 15 | NoNkeyUserSeedFound, | ||
| 16 | }; | ||
| 17 | 15 | ||
| 18 | pub const KeyTypePrefixByte = enum(u8) { | 16 | pub const KeyTypePrefixByte = enum(u8) { |
| 19 | seed = 18 << 3, // S | 17 | seed = 18 << 3, // S |
| @@ -28,7 +26,7 @@ pub const PublicPrefixByte = enum(u8) { | |||
| 28 | server = 13 << 3, // N | 26 | server = 13 << 3, // N |
| 29 | user = 20 << 3, // U | 27 | user = 20 << 3, // U |
| 30 | 28 | ||
| 31 | fn fromU8(b: u8) !PublicPrefixByte { | 29 | fn fromU8(b: u8) error{InvalidPrefixByte}!PublicPrefixByte { |
| 32 | return switch (b) { | 30 | return switch (b) { |
| 33 | @enumToInt(PublicPrefixByte.server) => .server, | 31 | @enumToInt(PublicPrefixByte.server) => .server, |
| 34 | @enumToInt(PublicPrefixByte.cluster) => .cluster, | 32 | @enumToInt(PublicPrefixByte.cluster) => .cluster, |
| @@ -45,47 +43,47 @@ pub const SeedKeyPair = struct { | |||
| 45 | 43 | ||
| 46 | seed: text_seed, | 44 | seed: text_seed, |
| 47 | 45 | ||
| 48 | pub fn generate(prefix: PublicPrefixByte) !Self { | 46 | pub fn generate(prefix: PublicPrefixByte) Self { |
| 49 | var raw_seed: [Ed25519.seed_length]u8 = undefined; | 47 | var raw_seed: [Ed25519.seed_length]u8 = undefined; |
| 50 | crypto.random.bytes(&raw_seed); | 48 | crypto.random.bytes(&raw_seed); |
| 51 | defer wipeBytes(&raw_seed); | 49 | defer wipeBytes(&raw_seed); |
| 52 | 50 | ||
| 53 | return Self{ .seed = try encodeSeed(prefix, &raw_seed) }; | 51 | return Self{ .seed = encodeSeed(prefix, &raw_seed) }; |
| 54 | } | 52 | } |
| 55 | 53 | ||
| 56 | pub fn fromTextSeed(seed: *const text_seed) !Self { | 54 | pub fn fromTextSeed(seed: *const text_seed) SeedDecodeError!Self { |
| 57 | var decoded = try decodeSeed(seed); | 55 | var decoded = try decodeSeed(seed); |
| 58 | decoded.wipe(); | 56 | decoded.wipe(); |
| 59 | return Self{ .seed = seed.* }; | 57 | return Self{ .seed = seed.* }; |
| 60 | } | 58 | } |
| 61 | 59 | ||
| 62 | pub fn fromRawSeed(prefix: PublicPrefixByte, raw_seed: *const [Ed25519.seed_length]u8) !Self { | 60 | pub fn fromRawSeed(prefix: PublicPrefixByte, raw_seed: *const [Ed25519.seed_length]u8) Self { |
| 63 | return Self{ .seed = try encodeSeed(prefix, raw_seed) }; | 61 | return Self{ .seed = encodeSeed(prefix, raw_seed) }; |
| 64 | } | 62 | } |
| 65 | 63 | ||
| 66 | fn rawSeed(self: *const Self) ![Ed25519.seed_length]u8 { | 64 | fn rawSeed(self: *const Self) SeedDecodeError![Ed25519.seed_length]u8 { |
| 67 | return (try decodeSeed(&self.seed)).seed; | 65 | return (try decodeSeed(&self.seed)).seed; |
| 68 | } | 66 | } |
| 69 | 67 | ||
| 70 | fn keys(self: *const Self) !Ed25519.KeyPair { | 68 | fn keys(self: *const Self) (SeedDecodeError || crypto.errors.IdentityElementError)!Ed25519.KeyPair { |
| 71 | return Ed25519.KeyPair.create(try rawSeed(self)); | 69 | return Ed25519.KeyPair.create(try rawSeed(self)); |
| 72 | } | 70 | } |
| 73 | 71 | ||
| 74 | pub fn privateKey(self: *const Self) !text_private { | 72 | pub fn privateKey(self: *const Self) (SeedDecodeError || crypto.errors.IdentityElementError)!text_private { |
| 75 | var kp = try self.keys(); | 73 | var kp = try self.keys(); |
| 76 | defer wipeKeyPair(&kp); | 74 | defer wipeKeyPair(&kp); |
| 77 | return try encodePrivate(&kp.secret_key); | 75 | return encodePrivate(&kp.secret_key); |
| 78 | } | 76 | } |
| 79 | 77 | ||
| 80 | pub fn publicKey(self: *const Self) !text_public { | 78 | pub fn publicKey(self: *const Self) (SeedDecodeError || crypto.errors.IdentityElementError)!text_public { |
| 81 | var decoded = try decodeSeed(&self.seed); | 79 | var decoded = try decodeSeed(&self.seed); |
| 82 | defer decoded.wipe(); | 80 | defer decoded.wipe(); |
| 83 | var kp = try Ed25519.KeyPair.create(decoded.seed); | 81 | var kp = try Ed25519.KeyPair.create(decoded.seed); |
| 84 | defer wipeKeyPair(&kp); | 82 | defer wipeKeyPair(&kp); |
| 85 | return try encodePublic(decoded.prefix, &kp.public_key); | 83 | return encodePublic(decoded.prefix, &kp.public_key); |
| 86 | } | 84 | } |
| 87 | 85 | ||
| 88 | pub fn intoPublicKey(self: *const Self) !PublicKey { | 86 | pub fn intoPublicKey(self: *const Self) (SeedDecodeError || crypto.errors.IdentityElementError)!PublicKey { |
| 89 | var decoded = try decodeSeed(&self.seed); | 87 | var decoded = try decodeSeed(&self.seed); |
| 90 | defer decoded.wipe(); | 88 | defer decoded.wipe(); |
| 91 | var kp = try Ed25519.KeyPair.create(decoded.seed); | 89 | var kp = try Ed25519.KeyPair.create(decoded.seed); |
| @@ -96,13 +94,19 @@ pub const SeedKeyPair = struct { | |||
| 96 | }; | 94 | }; |
| 97 | } | 95 | } |
| 98 | 96 | ||
| 97 | pub const SignError = SeedDecodeError || crypto.errors.IdentityElementError || crypto.errors.WeakPublicKeyError; | ||
| 98 | |||
| 99 | pub fn sign( | 99 | pub fn sign( |
| 100 | self: *const Self, | 100 | self: *const Self, |
| 101 | msg: []const u8, | 101 | msg: []const u8, |
| 102 | ) ![Ed25519.signature_length]u8 { | 102 | ) SignError![Ed25519.signature_length]u8 { |
| 103 | var kp = try self.keys(); | 103 | var kp = try self.keys(); |
| 104 | defer wipeKeyPair(&kp); | 104 | defer wipeKeyPair(&kp); |
| 105 | return try Ed25519.sign(msg, kp, null); | 105 | return Ed25519.sign(msg, kp, null) catch |e| switch (e) { |
| 106 | error.KeyMismatch => unreachable, // would mean that self.keys() has an incorrect implementation | ||
| 107 | error.WeakPublicKey => error.WeakPublicKey, | ||
| 108 | error.IdentityElement => error.IdentityElement, | ||
| 109 | }; | ||
| 106 | } | 110 | } |
| 107 | 111 | ||
| 108 | pub fn verify( | 112 | pub fn verify( |
| @@ -112,7 +116,7 @@ pub const SeedKeyPair = struct { | |||
| 112 | ) !void { | 116 | ) !void { |
| 113 | var kp = try self.keys(); | 117 | var kp = try self.keys(); |
| 114 | defer wipeKeyPair(&kp); | 118 | defer wipeKeyPair(&kp); |
| 115 | try Ed25519.verify(sig, msg, kp.public_key); | 119 | Ed25519.verify(sig, msg, kp.public_key) catch return error.InvalidSignature; |
| 116 | } | 120 | } |
| 117 | 121 | ||
| 118 | pub fn wipe(self: *Self) void { | 122 | pub fn wipe(self: *Self) void { |
| @@ -134,18 +138,17 @@ pub const PublicKey = struct { | |||
| 134 | prefix: PublicPrefixByte, | 138 | prefix: PublicPrefixByte, |
| 135 | key: [Ed25519.public_length]u8, | 139 | key: [Ed25519.public_length]u8, |
| 136 | 140 | ||
| 137 | pub fn fromTextPublicKey(text: *const text_public) !PublicKey { | 141 | pub fn fromTextPublicKey(text: *const text_public) DecodeError!PublicKey { |
| 138 | var decoded = try decode(1, Ed25519.public_length, text); | 142 | var decoded = try decode(1, Ed25519.public_length, text); |
| 139 | defer decoded.wipe(); // gets copied | 143 | defer decoded.wipe(); // gets copied |
| 140 | |||
| 141 | return PublicKey{ | 144 | return PublicKey{ |
| 142 | .prefix = try PublicPrefixByte.fromU8(decoded.prefix[0]), | 145 | .prefix = try PublicPrefixByte.fromU8(decoded.prefix[0]), |
| 143 | .key = decoded.data, | 146 | .key = decoded.data, |
| 144 | }; | 147 | }; |
| 145 | } | 148 | } |
| 146 | 149 | ||
| 147 | pub fn publicKey(self: *const Self) !text_public { | 150 | pub fn publicKey(self: *const Self) text_public { |
| 148 | return try encodePublic(self.prefix, &self.key); | 151 | return encodePublic(self.prefix, &self.key); |
| 149 | } | 152 | } |
| 150 | 153 | ||
| 151 | pub fn verify( | 154 | pub fn verify( |
| @@ -153,7 +156,7 @@ pub const PublicKey = struct { | |||
| 153 | msg: []const u8, | 156 | msg: []const u8, |
| 154 | sig: [Ed25519.signature_length]u8, | 157 | sig: [Ed25519.signature_length]u8, |
| 155 | ) !void { | 158 | ) !void { |
| 156 | try Ed25519.verify(sig, msg, self.key); | 159 | Ed25519.verify(sig, msg, self.key) catch return error.InvalidSignature; |
| 157 | } | 160 | } |
| 158 | 161 | ||
| 159 | pub fn wipe(self: *Self) void { | 162 | pub fn wipe(self: *Self) void { |
| @@ -177,11 +180,11 @@ pub const text_private = [text_private_len]u8; | |||
| 177 | pub const text_public = [text_public_len]u8; | 180 | pub const text_public = [text_public_len]u8; |
| 178 | pub const text_seed = [text_seed_len]u8; | 181 | pub const text_seed = [text_seed_len]u8; |
| 179 | 182 | ||
| 180 | pub fn encodePublic(prefix: PublicPrefixByte, key: *const [Ed25519.public_length]u8) !text_public { | 183 | fn encodePublic(prefix: PublicPrefixByte, key: *const [Ed25519.public_length]u8) text_public { |
| 181 | return encode(1, key.len, &[_]u8{@enumToInt(prefix)}, key); | 184 | return encode(1, key.len, &[_]u8{@enumToInt(prefix)}, key); |
| 182 | } | 185 | } |
| 183 | 186 | ||
| 184 | pub fn encodePrivate(key: *const [Ed25519.secret_length]u8) !text_private { | 187 | fn encodePrivate(key: *const [Ed25519.secret_length]u8) text_private { |
| 185 | return encode(1, key.len, &[_]u8{@enumToInt(KeyTypePrefixByte.private)}, key); | 188 | return encode(1, key.len, &[_]u8{@enumToInt(KeyTypePrefixByte.private)}, key); |
| 186 | } | 189 | } |
| 187 | 190 | ||
| @@ -194,7 +197,7 @@ fn encode( | |||
| 194 | comptime data_len: usize, | 197 | comptime data_len: usize, |
| 195 | prefix: *const [prefix_len]u8, | 198 | prefix: *const [prefix_len]u8, |
| 196 | data: *const [data_len]u8, | 199 | data: *const [data_len]u8, |
| 197 | ) !encoded_key(prefix_len, data_len) { | 200 | ) encoded_key(prefix_len, data_len) { |
| 198 | var buf: [prefix_len + data_len + 2]u8 = undefined; | 201 | var buf: [prefix_len + data_len + 2]u8 = undefined; |
| 199 | defer wipeBytes(&buf); | 202 | defer wipeBytes(&buf); |
| 200 | 203 | ||
| @@ -210,7 +213,7 @@ fn encode( | |||
| 210 | return text; | 213 | return text; |
| 211 | } | 214 | } |
| 212 | 215 | ||
| 213 | pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8) !text_seed { | 216 | pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8) text_seed { |
| 214 | const full_prefix = &[_]u8{ | 217 | const full_prefix = &[_]u8{ |
| 215 | @enumToInt(KeyTypePrefixByte.seed) | (@enumToInt(prefix) >> 5), | 218 | @enumToInt(KeyTypePrefixByte.seed) | (@enumToInt(prefix) >> 5), |
| 216 | (@enumToInt(prefix) & 0b00011111) << 3, | 219 | (@enumToInt(prefix) & 0b00011111) << 3, |
| @@ -218,21 +221,7 @@ pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8) | |||
| 218 | return encode(full_prefix.len, src.len, full_prefix, src); | 221 | return encode(full_prefix.len, src.len, full_prefix, src); |
| 219 | } | 222 | } |
| 220 | 223 | ||
| 221 | pub fn decodePrivate(text: *const text_private) ![Ed25519.secret_length]u8 { | 224 | pub const DecodeError = InvalidPrefixByteError || base32.DecodeError || crc16.InvalidChecksumError; |
| 222 | var decoded = try decode(1, Ed25519.secret_length, text); | ||
| 223 | defer decoded.wipe(); | ||
| 224 | if (decoded.prefix[0] != @enumToInt(KeyTypePrefixByte.private)) | ||
| 225 | return error.InvalidPrefixByte; | ||
| 226 | return decoded.data; | ||
| 227 | } | ||
| 228 | |||
| 229 | pub fn decodePublic(prefix: PublicPrefixByte, text: *const text_public) ![Ed25519.public_length]u8 { | ||
| 230 | var decoded = try decode(1, Ed25519.public_length, text); | ||
| 231 | defer decoded.wipe(); | ||
| 232 | if (decoded.data[0] != @enumToInt(prefix)) | ||
| 233 | return error.InvalidPrefixByte; | ||
| 234 | return decoded.data; | ||
| 235 | } | ||
| 236 | 225 | ||
| 237 | fn DecodedNkey(comptime prefix_len: usize, comptime data_len: usize) type { | 226 | fn DecodedNkey(comptime prefix_len: usize, comptime data_len: usize) type { |
| 238 | return struct { | 227 | return struct { |
| @@ -252,7 +241,7 @@ fn decode( | |||
| 252 | comptime prefix_len: usize, | 241 | comptime prefix_len: usize, |
| 253 | comptime data_len: usize, | 242 | comptime data_len: usize, |
| 254 | text: *const [base32.Encoder.calcSize(prefix_len + data_len + 2)]u8, | 243 | text: *const [base32.Encoder.calcSize(prefix_len + data_len + 2)]u8, |
| 255 | ) !DecodedNkey(prefix_len, data_len) { | 244 | ) (base32.DecodeError || crc16.InvalidChecksumError)!DecodedNkey(prefix_len, data_len) { |
| 256 | var raw: [prefix_len + data_len + 2]u8 = undefined; | 245 | var raw: [prefix_len + data_len + 2]u8 = undefined; |
| 257 | defer wipeBytes(&raw); | 246 | defer wipeBytes(&raw); |
| 258 | std.debug.assert((try base32.Decoder.decode(&raw, text[0..])).len == raw.len); | 247 | std.debug.assert((try base32.Decoder.decode(&raw, text[0..])).len == raw.len); |
| @@ -278,7 +267,9 @@ pub const DecodedSeed = struct { | |||
| 278 | } | 267 | } |
| 279 | }; | 268 | }; |
| 280 | 269 | ||
| 281 | pub fn decodeSeed(text: *const text_seed) !DecodedSeed { | 270 | pub const SeedDecodeError = DecodeError || InvalidSeedError; |
| 271 | |||
| 272 | pub fn decodeSeed(text: *const text_seed) SeedDecodeError!DecodedSeed { | ||
| 282 | var decoded = try decode(2, Ed25519.seed_length, text); | 273 | var decoded = try decode(2, Ed25519.seed_length, text); |
| 283 | defer decoded.wipe(); // gets copied | 274 | defer decoded.wipe(); // gets copied |
| 284 | 275 | ||
| @@ -328,65 +319,50 @@ pub fn isValidPublicKey(text: *const text_public, with_type: ?PublicPrefixByte) | |||
| 328 | return if (with_type) |ty| public == ty else true; | 319 | return if (with_type) |ty| public == ty else true; |
| 329 | } | 320 | } |
| 330 | 321 | ||
| 331 | pub fn getNextLine(text: []const u8, off: *usize) ?[]const u8 { | ||
| 332 | if (off.* >= text.len) return null; | ||
| 333 | const newline_pos = mem.indexOfPos(u8, text, off.*, "\n") orelse return null; | ||
| 334 | const start = off.*; | ||
| 335 | var end = newline_pos; | ||
| 336 | if (newline_pos > 0 and text[newline_pos - 1] == '\r') end -= 1; | ||
| 337 | off.* = newline_pos + 1; | ||
| 338 | return text[start..end]; | ||
| 339 | } | ||
| 340 | |||
| 341 | // `line` must not contain CR or LF characters. | 322 | // `line` must not contain CR or LF characters. |
| 342 | pub fn isKeySectionBarrier(line: []const u8) bool { | 323 | pub fn isKeySectionBarrier(line: []const u8) bool { |
| 343 | return line.len >= 6 and mem.startsWith(u8, line, "---") and mem.endsWith(u8, line, "---"); | 324 | return line.len >= 6 and mem.startsWith(u8, line, "---") and mem.endsWith(u8, line, "---"); |
| 344 | } | 325 | } |
| 345 | 326 | ||
| 346 | pub fn areKeySectionContentsValid(contents: []const u8) bool { | 327 | const allowed_creds_section_chars_table: [256]bool = allowed: { |
| 347 | const allowed_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.="; | 328 | @setEvalBranchQuota(256); |
| 348 | 329 | ||
| 349 | for (contents) |c| { | 330 | var table = [_]bool{false} ** 256; |
| 350 | var is_c_allowed = false; | 331 | const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.="; |
| 351 | for (allowed_chars) |allowed_c| { | 332 | for (chars) |char| table[char] = true; |
| 352 | if (c == allowed_c) { | ||
| 353 | is_c_allowed = true; | ||
| 354 | break; | ||
| 355 | } | ||
| 356 | } | ||
| 357 | if (!is_c_allowed) return false; | ||
| 358 | } | ||
| 359 | 333 | ||
| 334 | break :allowed table; | ||
| 335 | }; | ||
| 336 | |||
| 337 | pub fn areKeySectionContentsValid(contents: []const u8) bool { | ||
| 338 | for (contents) |c| if (!allowed_creds_section_chars_table[c]) return false; | ||
| 360 | return true; | 339 | return true; |
| 361 | } | 340 | } |
| 362 | 341 | ||
| 363 | pub fn findKeySection(text: []const u8, off: *usize) ?[]const u8 { | 342 | pub fn findKeySection(text: []const u8, line_it: *std.mem.SplitIterator) ?[]const u8 { |
| 364 | // Skip all space | 343 | // TODO(rutgerbrf): There is a weird edge case in the github.com/nats-io/nkeys library, |
| 365 | // Lines end with \n, but \r\n is also fine | 344 | // see https://regex101.com/r/pEaqcJ/1. It allows the opening barrier to start at an |
| 366 | // Contents of the key may consist of abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.= | 345 | // arbitrary point on the line, meaning that `asdf-----BEGIN USER NKEY SEED-----` |
| 367 | // However, if a line seems to be in the form of ---stuff---, the section is ended. | 346 | // is regarded as a valid opening barrier by the library. |
| 368 | // A newline must be present at the end of the key footer | 347 | // Should we accept a creds file formatted in such a manner? |
| 369 | // See https://regex101.com/r/pEaqcJ/1 for a weird edge case in the github.com/nats-io/nkeys library | ||
| 370 | // Another weird edge case: https://regex101.com/r/Xmqj1h/1 | ||
| 371 | 348 | ||
| 372 | // TODO(rutgerbrf): switch to std.mem.SplitIterator | ||
| 373 | while (true) { | 349 | while (true) { |
| 374 | const opening_line = getNextLine(text, off) orelse return null; | 350 | const opening_line = line_it.next() orelse return null; |
| 375 | if (!isKeySectionBarrier(opening_line)) continue; | 351 | if (!isKeySectionBarrier(opening_line)) continue; |
| 376 | 352 | ||
| 377 | const contents_line = getNextLine(text, off) orelse return null; | 353 | const contents_line = line_it.next() orelse return null; |
| 378 | if (!areKeySectionContentsValid(contents_line)) continue; | 354 | if (!areKeySectionContentsValid(contents_line)) continue; |
| 379 | 355 | ||
| 380 | const closing_line = getNextLine(text, off) orelse return null; | 356 | const closing_line = line_it.next() orelse return null; |
| 381 | if (!isKeySectionBarrier(closing_line)) continue; | 357 | if (!isKeySectionBarrier(closing_line)) continue; |
| 382 | 358 | ||
| 383 | return contents_line; | 359 | return contents_line; |
| 384 | } | 360 | } |
| 385 | } | 361 | } |
| 386 | 362 | ||
| 387 | pub fn parseDecoratedJwt(contents: []const u8) ![]const u8 { | 363 | pub fn parseDecoratedJwt(contents: []const u8) []const u8 { |
| 388 | var current_off: usize = 0; | 364 | var line_it = mem.split(contents, "\n"); |
| 389 | return findKeySection(contents, ¤t_off) orelse return contents; | 365 | return findKeySection(contents, &line_it) orelse return contents; |
| 390 | } | 366 | } |
| 391 | 367 | ||
| 392 | fn validNkey(text: []const u8) bool { | 368 | fn validNkey(text: []const u8) bool { |
| @@ -399,9 +375,9 @@ fn validNkey(text: []const u8) bool { | |||
| 399 | } | 375 | } |
| 400 | 376 | ||
| 401 | fn findNkey(text: []const u8) ?[]const u8 { | 377 | fn findNkey(text: []const u8) ?[]const u8 { |
| 378 | var line_it = std.mem.split(text, "\n"); | ||
| 402 | var current_off: usize = 0; | 379 | var current_off: usize = 0; |
| 403 | while (true) { | 380 | while (line_it.next()) |line| { |
| 404 | var line = getNextLine(text, ¤t_off) orelse return null; | ||
| 405 | for (line) |c, i| { | 381 | for (line) |c, i| { |
| 406 | if (!ascii.isSpace(c)) { | 382 | if (!ascii.isSpace(c)) { |
| 407 | if (validNkey(line[i..])) return line[i..]; | 383 | if (validNkey(line[i..])) return line[i..]; |
| @@ -409,21 +385,23 @@ fn findNkey(text: []const u8) ?[]const u8 { | |||
| 409 | } | 385 | } |
| 410 | } | 386 | } |
| 411 | } | 387 | } |
| 388 | return null; | ||
| 412 | } | 389 | } |
| 413 | 390 | ||
| 414 | pub fn parseDecoratedNkey(contents: []const u8) !SeedKeyPair { | 391 | pub fn parseDecoratedNkey(contents: []const u8) NoNkeySeedFoundError!SeedKeyPair { |
| 392 | var line_it = mem.split(contents, "\n"); | ||
| 415 | var current_off: usize = 0; | 393 | var current_off: usize = 0; |
| 416 | var seed: ?[]const u8 = null; | 394 | var seed: ?[]const u8 = null; |
| 417 | if (findKeySection(contents, ¤t_off) != null) | 395 | if (findKeySection(contents, &line_it) != null) |
| 418 | seed = findKeySection(contents, ¤t_off); | 396 | seed = findKeySection(contents, &line_it); |
| 419 | if (seed == null) | 397 | if (seed == null) |
| 420 | seed = findNkey(contents) orelse return error.NoNkeySeedFound; | 398 | seed = findNkey(contents) orelse return error.NoNkeySeedFound; |
| 421 | if (!validNkey(seed.?)) | 399 | if (!validNkey(seed.?)) |
| 422 | return error.NoNkeySeedFound; | 400 | return error.NoNkeySeedFound; |
| 423 | return SeedKeyPair.fromTextSeed(seed.?[0..text_seed_len]); | 401 | return SeedKeyPair.fromTextSeed(seed.?[0..text_seed_len]) catch return error.NoNkeySeedFound; |
| 424 | } | 402 | } |
| 425 | 403 | ||
| 426 | pub fn parseDecoratedUserNkey(contents: []const u8) !SeedKeyPair { | 404 | pub fn parseDecoratedUserNkey(contents: []const u8) (NoNkeySeedFoundError || NoNkeyUserSeedFoundError)!SeedKeyPair { |
| 427 | var key = try parseDecoratedNkey(contents); | 405 | var key = try parseDecoratedNkey(contents); |
| 428 | if (!mem.startsWith(u8, &key.seed, "SU")) return error.NoNkeyUserSeedFound; | 406 | if (!mem.startsWith(u8, &key.seed, "SU")) return error.NoNkeyUserSeedFound; |
| 429 | defer key.wipe(); | 407 | defer key.wipe(); |
| @@ -437,12 +415,12 @@ test { | |||
| 437 | } | 415 | } |
| 438 | 416 | ||
| 439 | test { | 417 | test { |
| 440 | var key_pair = try SeedKeyPair.generate(PublicPrefixByte.server); | 418 | var key_pair = SeedKeyPair.generate(PublicPrefixByte.server); |
| 441 | defer key_pair.wipe(); | 419 | defer key_pair.wipe(); |
| 442 | 420 | ||
| 443 | var decoded_seed = try decodeSeed(&key_pair.seed); | 421 | var decoded_seed = try decodeSeed(&key_pair.seed); |
| 444 | defer decoded_seed.wipe(); | 422 | defer decoded_seed.wipe(); |
| 445 | var encoded_second_time = try encodeSeed(decoded_seed.prefix, &decoded_seed.seed); | 423 | var encoded_second_time = encodeSeed(decoded_seed.prefix, &decoded_seed.seed); |
| 446 | defer wipeBytes(&encoded_second_time); | 424 | defer wipeBytes(&encoded_second_time); |
| 447 | try testing.expectEqualSlices(u8, &key_pair.seed, &encoded_second_time); | 425 | try testing.expectEqualSlices(u8, &key_pair.seed, &encoded_second_time); |
| 448 | try testing.expect(isValidEncoding(&key_pair.seed)); | 426 | try testing.expect(isValidEncoding(&key_pair.seed)); |
| @@ -458,7 +436,7 @@ test { | |||
| 458 | 436 | ||
| 459 | var pub_key = try key_pair.intoPublicKey(); | 437 | var pub_key = try key_pair.intoPublicKey(); |
| 460 | defer pub_key.wipe(); | 438 | defer pub_key.wipe(); |
| 461 | var pub_key_str_b = try pub_key.publicKey(); | 439 | var pub_key_str_b = pub_key.publicKey(); |
| 462 | defer wipeBytes(&pub_key_str_b); | 440 | defer wipeBytes(&pub_key_str_b); |
| 463 | try testing.expectEqualSlices(u8, &pub_key_str_a, &pub_key_str_b); | 441 | try testing.expectEqualSlices(u8, &pub_key_str_a, &pub_key_str_b); |
| 464 | } | 442 | } |
| @@ -470,5 +448,5 @@ test { | |||
| 470 | 448 | ||
| 471 | // TODO(rutgerbrf): validate the contents of the results of these functions | 449 | // TODO(rutgerbrf): validate the contents of the results of these functions |
| 472 | _ = try parseDecoratedUserNkey(creds_bytes); | 450 | _ = try parseDecoratedUserNkey(creds_bytes); |
| 473 | _ = try parseDecoratedJwt(creds_bytes); | 451 | _ = parseDecoratedJwt(creds_bytes); |
| 474 | } | 452 | } |
diff --git a/src/znk.zig b/src/znk.zig index 1c2898b..5a5ab5e 100644 --- a/src/znk.zig +++ b/src/znk.zig | |||
| @@ -147,7 +147,7 @@ pub fn cmdGen(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !voi | |||
| 147 | 147 | ||
| 148 | try PrefixKeyGenerator.init(arena, ty.?, capitalized_prefix).generate(); | 148 | try PrefixKeyGenerator.init(arena, ty.?, capitalized_prefix).generate(); |
| 149 | } else { | 149 | } else { |
| 150 | var kp = nkeys.SeedKeyPair.generate(ty.?) catch |e| fatal("could not generate key pair: {e}", .{e}); | 150 | var kp = nkeys.SeedKeyPair.generate(ty.?); |
| 151 | defer kp.wipe(); | 151 | defer kp.wipe(); |
| 152 | try stdout.writeAll(&kp.seed); | 152 | try stdout.writeAll(&kp.seed); |
| 153 | try stdout.writeAll("\n"); | 153 | try stdout.writeAll("\n"); |
| @@ -231,7 +231,7 @@ pub fn cmdSign(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !vo | |||
| 231 | const content = file.?.readToEndAlloc(arena, std.math.maxInt(usize)) catch { | 231 | const content = file.?.readToEndAlloc(arena, std.math.maxInt(usize)) catch { |
| 232 | fatal("could not read file to generate signature for", .{}); | 232 | fatal("could not read file to generate signature for", .{}); |
| 233 | }; | 233 | }; |
| 234 | var kp = switch (readKeyFile(arena, key.?)) { | 234 | var kp = switch (readKeyFile(arena, key.?) orelse fatal("could not find a valid key", .{})) { |
| 235 | .seed_key_pair => |kp| kp, | 235 | .seed_key_pair => |kp| kp, |
| 236 | else => |*k| { | 236 | else => |*k| { |
| 237 | k.wipe(); | 237 | k.wipe(); |
| @@ -339,7 +339,7 @@ pub fn cmdVerify(gpa: *Allocator, arena: *Allocator, args: []const []const u8) ! | |||
| 339 | const signature_b64 = sig.?.readToEndAlloc(arena, std.math.maxInt(usize)) catch { | 339 | const signature_b64 = sig.?.readToEndAlloc(arena, std.math.maxInt(usize)) catch { |
| 340 | fatal("could not read signature", .{}); | 340 | fatal("could not read signature", .{}); |
| 341 | }; | 341 | }; |
| 342 | var k = readKeyFile(arena, key.?); | 342 | var k = readKeyFile(arena, key.?) orelse fatal("could not find a valid key", .{}); |
| 343 | defer k.wipe(); | 343 | defer k.wipe(); |
| 344 | 344 | ||
| 345 | const trimmed_signature_b64 = mem.trim(u8, signature_b64, " \n\t\r"); | 345 | const trimmed_signature_b64 = mem.trim(u8, signature_b64, " \n\t\r"); |
| @@ -381,7 +381,7 @@ const PrefixKeyGenerator = struct { | |||
| 381 | while (true) { | 381 | while (true) { |
| 382 | if (self.done.load(.SeqCst)) return; | 382 | if (self.done.load(.SeqCst)) return; |
| 383 | 383 | ||
| 384 | var kp = nkeys.SeedKeyPair.generate(self.ty) catch |e| fatal("could not generate key pair: {e}", .{e}); | 384 | var kp = nkeys.SeedKeyPair.generate(self.ty); |
| 385 | defer kp.wipe(); | 385 | defer kp.wipe(); |
| 386 | var public_key = kp.publicKey() catch |e| fatal("could not generate public key: {e}", .{e}); | 386 | var public_key = kp.publicKey() catch |e| fatal("could not generate public key: {e}", .{e}); |
| 387 | if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue; | 387 | if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue; |
| @@ -435,7 +435,7 @@ pub const Nkey = union(enum) { | |||
| 435 | pub fn publicKey(self: *const Self) !nkeys.text_public { | 435 | pub fn publicKey(self: *const Self) !nkeys.text_public { |
| 436 | return switch (self.*) { | 436 | return switch (self.*) { |
| 437 | .seed_key_pair => |*kp| try kp.publicKey(), | 437 | .seed_key_pair => |*kp| try kp.publicKey(), |
| 438 | .public_key => |*pk| try pk.publicKey(), | 438 | .public_key => |*pk| pk.publicKey(), |
| 439 | }; | 439 | }; |
| 440 | } | 440 | } |
| 441 | 441 | ||
| @@ -481,20 +481,23 @@ pub const Nkey = union(enum) { | |||
| 481 | } | 481 | } |
| 482 | }; | 482 | }; |
| 483 | 483 | ||
| 484 | pub fn readKeyFile(allocator: *Allocator, file: fs.File) Nkey { | 484 | pub fn readKeyFile(allocator: *Allocator, file: fs.File) ?Nkey { |
| 485 | var bytes = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch fatal("could not read key file", .{}); | 485 | var bytes = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch fatal("could not read key file", .{}); |
| 486 | defer { | ||
| 487 | for (bytes) |*b| b.* = 0; | ||
| 488 | allocator.free(bytes); | ||
| 489 | } | ||
| 486 | 490 | ||
| 487 | var iterator = mem.split(bytes, "\n"); | 491 | var iterator = mem.split(bytes, "\n"); |
| 488 | while (iterator.next()) |line| { | 492 | while (iterator.next()) |line| { |
| 489 | if (nkeys.isValidEncoding(line) and line.len == nkeys.text_seed_len) { | 493 | if (nkeys.isValidEncoding(line) and line.len == nkeys.text_seed_len) { |
| 490 | var k = Nkey.fromText(line) catch continue; | 494 | var k = Nkey.fromText(line) catch continue; |
| 491 | defer k.wipe(); | 495 | defer k.wipe(); |
| 492 | allocator.free(bytes); | ||
| 493 | return k; | 496 | return k; |
| 494 | } | 497 | } |
| 495 | } | 498 | } |
| 496 | 499 | ||
| 497 | fatal("could not find a valid key", .{}); | 500 | return null; |
| 498 | } | 501 | } |
| 499 | 502 | ||
| 500 | test { | 503 | test { |