diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/base32.zig | 14 | ||||
| -rw-r--r-- | src/nkeys.zig | 189 | ||||
| -rw-r--r-- | src/znk.zig | 93 |
3 files changed, 151 insertions, 145 deletions
diff --git a/src/base32.zig b/src/base32.zig index d8adfd5..d612e7a 100644 --- a/src/base32.zig +++ b/src/base32.zig | |||
| @@ -182,16 +182,14 @@ pub const Decoder = struct { | |||
| 182 | 182 | ||
| 183 | /// Get a character from the buffer. | 183 | /// Get a character from the buffer. |
| 184 | fn decodeChar(c: u8) DecodeError!u5 { | 184 | fn decodeChar(c: u8) DecodeError!u5 { |
| 185 | var value: u5 = 0; | ||
| 186 | if (c >= 'A' and c <= 'Z') { | 185 | if (c >= 'A' and c <= 'Z') { |
| 187 | value = @truncate(u5, c - @as(u8, 'A')); | 186 | return @truncate(u5, c - @as(u8, 'A')); |
| 188 | } else if (c >= '2' and c <= '9') { | 187 | } else if (c >= '2' and c <= '9') { |
| 189 | // '2' -> 26 | 188 | // '2' -> 26 |
| 190 | value = @truncate(u5, c - @as(u8, '2') + 26); | 189 | return @truncate(u5, c - @as(u8, '2') + 26); |
| 191 | } else { | 190 | } else { |
| 192 | return error.CorruptInputError; | 191 | return error.CorruptInputError; |
| 193 | } | 192 | } |
| 194 | return value; | ||
| 195 | } | 193 | } |
| 196 | 194 | ||
| 197 | /// Get the next 5-bit decoded character, read from `self.buffer`. | 195 | /// Get the next 5-bit decoded character, read from `self.buffer`. |
| @@ -248,12 +246,12 @@ test { | |||
| 248 | const decoded = "this is a test"; | 246 | const decoded = "this is a test"; |
| 249 | 247 | ||
| 250 | var decode_buf: [Decoder.calcSize(encoded.len)]u8 = undefined; | 248 | var decode_buf: [Decoder.calcSize(encoded.len)]u8 = undefined; |
| 251 | var decode_res = try Decoder.decode(&decode_buf, encoded); | 249 | const decode_res = try Decoder.decode(&decode_buf, encoded); |
| 252 | 250 | ||
| 253 | try testing.expectEqualStrings(decoded, decode_res); | 251 | try testing.expectEqualStrings(decoded, decode_res); |
| 254 | 252 | ||
| 255 | var encode_buf: [Encoder.calcSize(decoded.len)]u8 = undefined; | 253 | var encode_buf: [Encoder.calcSize(decoded.len)]u8 = undefined; |
| 256 | var encode_res = Encoder.encode(&encode_buf, decoded); | 254 | const encode_res = Encoder.encode(&encode_buf, decoded); |
| 257 | 255 | ||
| 258 | try testing.expectEqualStrings(encoded, encode_res); | 256 | try testing.expectEqualStrings(encoded, encode_res); |
| 259 | } | 257 | } |
| @@ -263,12 +261,12 @@ test { | |||
| 263 | const decoded = &[_]u8{ 0x93, 0x40, 0x7f, 0x90, 0xfd, 0xbf, 0x1f, 0xd8, 0xe9, 0x9a, 0x89, 0x8b, 0xb5, 0xd4, 0x0b, 0xf3, 0x62, 0x54, 0x5d, 0x6d, 0xd1, 0x3d, 0xf7, 0x78, 0xad, 0x8d, 0x21, 0xc4, 0x8a, 0x01, 0x7a, 0xfd, 0xc3, 0x10, 0x2f, 0x5e }; | 261 | const decoded = &[_]u8{ 0x93, 0x40, 0x7f, 0x90, 0xfd, 0xbf, 0x1f, 0xd8, 0xe9, 0x9a, 0x89, 0x8b, 0xb5, 0xd4, 0x0b, 0xf3, 0x62, 0x54, 0x5d, 0x6d, 0xd1, 0x3d, 0xf7, 0x78, 0xad, 0x8d, 0x21, 0xc4, 0x8a, 0x01, 0x7a, 0xfd, 0xc3, 0x10, 0x2f, 0x5e }; |
| 264 | 262 | ||
| 265 | var decode_buf: [Decoder.calcSize(encoded.len)]u8 = undefined; | 263 | var decode_buf: [Decoder.calcSize(encoded.len)]u8 = undefined; |
| 266 | var decode_res = try Decoder.decode(&decode_buf, encoded); | 264 | const decode_res = try Decoder.decode(&decode_buf, encoded); |
| 267 | 265 | ||
| 268 | try testing.expectEqualSlices(u8, decoded, decode_res); | 266 | try testing.expectEqualSlices(u8, decoded, decode_res); |
| 269 | 267 | ||
| 270 | var encode_buf: [Encoder.calcSize(decoded.len)]u8 = undefined; | 268 | var encode_buf: [Encoder.calcSize(decoded.len)]u8 = undefined; |
| 271 | var encode_res = Encoder.encode(&encode_buf, decoded); | 269 | const encode_res = Encoder.encode(&encode_buf, decoded); |
| 272 | 270 | ||
| 273 | try testing.expectEqualSlices(u8, encoded, encode_res); | 271 | try testing.expectEqualSlices(u8, encoded, encode_res); |
| 274 | } | 272 | } |
diff --git a/src/nkeys.zig b/src/nkeys.zig index 8806a81..1880fa8 100644 --- a/src/nkeys.zig +++ b/src/nkeys.zig | |||
| @@ -11,63 +11,8 @@ const Error = error{ | |||
| 11 | InvalidPrefixByte, | 11 | InvalidPrefixByte, |
| 12 | InvalidEncoding, | 12 | InvalidEncoding, |
| 13 | InvalidSeed, | 13 | InvalidSeed, |
| 14 | NoNKeySeedFound, | 14 | NoNkeySeedFound, |
| 15 | NoNKeyUserSeedFound, | 15 | NoNkeyUserSeedFound, |
| 16 | }; | ||
| 17 | |||
| 18 | pub fn fromText(text: []const u8) !Key { | ||
| 19 | if (!isValidEncoding(text)) return error.InvalidEncoding; | ||
| 20 | switch (text[0]) { | ||
| 21 | 'S' => { | ||
| 22 | // It's a seed. | ||
| 23 | if (text.len != text_seed_len) return error.InvalidSeed; | ||
| 24 | return Key{ .seed_key_pair = try fromSeed(text[0..text_seed_len]) }; | ||
| 25 | }, | ||
| 26 | 'P' => return error.InvalidEncoding, // unsupported for now | ||
| 27 | else => { | ||
| 28 | if (text.len != text_public_len) return error.InvalidEncoding; | ||
| 29 | return Key{ .public_key = try fromPublicKey(text[0..text_public_len]) }; | ||
| 30 | }, | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | pub const Key = union(enum) { | ||
| 35 | seed_key_pair: SeedKeyPair, | ||
| 36 | public_key: PublicKey, | ||
| 37 | |||
| 38 | const Self = @This(); | ||
| 39 | |||
| 40 | pub fn publicKey(self: *const Self) !text_public { | ||
| 41 | return switch (self.*) { | ||
| 42 | .seed_key_pair => |*kp| try kp.publicKey(), | ||
| 43 | .public_key => |*pk| try pk.publicKey(), | ||
| 44 | }; | ||
| 45 | } | ||
| 46 | |||
| 47 | pub fn intoPublicKey(self: *const Self) !PublicKey { | ||
| 48 | return switch (self.*) { | ||
| 49 | .seed_key_pair => |*kp| try kp.intoPublicKey(), | ||
| 50 | .public_key => |pk| pk, | ||
| 51 | }; | ||
| 52 | } | ||
| 53 | |||
| 54 | pub fn verify( | ||
| 55 | self: *const Self, | ||
| 56 | msg: []const u8, | ||
| 57 | sig: [Ed25519.signature_length]u8, | ||
| 58 | ) !void { | ||
| 59 | return switch (self.*) { | ||
| 60 | .seed_key_pair => |*kp| try kp.verify(msg, sig), | ||
| 61 | .public_key => |*pk| try pk.verify(msg, sig), | ||
| 62 | }; | ||
| 63 | } | ||
| 64 | |||
| 65 | pub fn wipe(self: *Self) void { | ||
| 66 | return switch (self.*) { | ||
| 67 | .seed_key_pair => |*kp| kp.wipe(), | ||
| 68 | .public_key => |*pk| pk.wipe(), | ||
| 69 | }; | ||
| 70 | } | ||
| 71 | }; | 16 | }; |
| 72 | 17 | ||
| 73 | pub const KeyTypePrefixByte = enum(u8) { | 18 | pub const KeyTypePrefixByte = enum(u8) { |
| @@ -100,22 +45,24 @@ pub const SeedKeyPair = struct { | |||
| 100 | 45 | ||
| 101 | seed: text_seed, | 46 | seed: text_seed, |
| 102 | 47 | ||
| 103 | pub fn init(prefix: PublicPrefixByte) !Self { | 48 | pub fn generate(prefix: PublicPrefixByte) !Self { |
| 104 | var raw_seed: [Ed25519.seed_length]u8 = undefined; | 49 | var raw_seed: [Ed25519.seed_length]u8 = undefined; |
| 105 | crypto.random.bytes(&raw_seed); | 50 | crypto.random.bytes(&raw_seed); |
| 106 | defer wipeBytes(&raw_seed); | 51 | defer wipeBytes(&raw_seed); |
| 107 | 52 | ||
| 108 | var seed = try encodeSeed(prefix, &raw_seed); | 53 | return Self{ .seed = try encodeSeed(prefix, &raw_seed) }; |
| 109 | return Self{ .seed = seed }; | ||
| 110 | } | 54 | } |
| 111 | 55 | ||
| 112 | pub fn initFromSeed(seed: *const text_seed) !Self { | 56 | pub fn fromTextSeed(seed: *const text_seed) !Self { |
| 113 | var decoded = try decodeSeed(seed); | 57 | var decoded = try decodeSeed(seed); |
| 114 | defer decoded.wipe(); | 58 | decoded.wipe(); |
| 115 | |||
| 116 | return Self{ .seed = seed.* }; | 59 | return Self{ .seed = seed.* }; |
| 117 | } | 60 | } |
| 118 | 61 | ||
| 62 | pub fn fromRawSeed(prefix: PublicPrefixByte, raw_seed: *const [Ed25519.seed_length]u8) !Self { | ||
| 63 | return Self{ .seed = try encodeSeed(prefix, raw_seed) }; | ||
| 64 | } | ||
| 65 | |||
| 119 | fn rawSeed(self: *const Self) ![Ed25519.seed_length]u8 { | 66 | fn rawSeed(self: *const Self) ![Ed25519.seed_length]u8 { |
| 120 | return (try decodeSeed(&self.seed)).seed; | 67 | return (try decodeSeed(&self.seed)).seed; |
| 121 | } | 68 | } |
| @@ -140,6 +87,7 @@ pub const SeedKeyPair = struct { | |||
| 140 | 87 | ||
| 141 | pub fn intoPublicKey(self: *const Self) !PublicKey { | 88 | pub fn intoPublicKey(self: *const Self) !PublicKey { |
| 142 | var decoded = try decodeSeed(&self.seed); | 89 | var decoded = try decodeSeed(&self.seed); |
| 90 | defer decoded.wipe(); | ||
| 143 | var kp = try Ed25519.KeyPair.create(decoded.seed); | 91 | var kp = try Ed25519.KeyPair.create(decoded.seed); |
| 144 | defer wipeKeyPair(&kp); | 92 | defer wipeKeyPair(&kp); |
| 145 | return PublicKey{ | 93 | return PublicKey{ |
| @@ -186,6 +134,16 @@ pub const PublicKey = struct { | |||
| 186 | prefix: PublicPrefixByte, | 134 | prefix: PublicPrefixByte, |
| 187 | key: [Ed25519.public_length]u8, | 135 | key: [Ed25519.public_length]u8, |
| 188 | 136 | ||
| 137 | pub fn fromTextPublicKey(text: *const text_public) !PublicKey { | ||
| 138 | var decoded = try decode(1, Ed25519.public_length, text); | ||
| 139 | defer decoded.wipe(); // gets copied | ||
| 140 | |||
| 141 | return PublicKey{ | ||
| 142 | .prefix = try PublicPrefixByte.fromU8(decoded.prefix[0]), | ||
| 143 | .key = decoded.data, | ||
| 144 | }; | ||
| 145 | } | ||
| 146 | |||
| 189 | pub fn publicKey(self: *const Self) !text_public { | 147 | pub fn publicKey(self: *const Self) !text_public { |
| 190 | return try encodePublic(self.prefix, &self.key); | 148 | return try encodePublic(self.prefix, &self.key); |
| 191 | } | 149 | } |
| @@ -199,8 +157,8 @@ pub const PublicKey = struct { | |||
| 199 | } | 157 | } |
| 200 | 158 | ||
| 201 | pub fn wipe(self: *Self) void { | 159 | pub fn wipe(self: *Self) void { |
| 202 | self.prefix = .user; | 160 | self.prefix = .account; |
| 203 | std.crypto.random.bytes(&self.key); | 161 | wipeBytes(&self.key); |
| 204 | } | 162 | } |
| 205 | }; | 163 | }; |
| 206 | 164 | ||
| @@ -247,22 +205,22 @@ fn encode( | |||
| 247 | mem.writeIntLittle(u16, buf[buf.len - 2 .. buf.len], checksum); | 205 | mem.writeIntLittle(u16, buf[buf.len - 2 .. buf.len], checksum); |
| 248 | 206 | ||
| 249 | var text: encoded_key(prefix_len, data_len) = undefined; | 207 | var text: encoded_key(prefix_len, data_len) = undefined; |
| 250 | _ = base32.Encoder.encode(&text, &buf); | 208 | std.debug.assert(base32.Encoder.encode(&text, &buf).len == text.len); |
| 251 | 209 | ||
| 252 | return text; | 210 | return text; |
| 253 | } | 211 | } |
| 254 | 212 | ||
| 255 | pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8) !text_seed { | 213 | pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8) !text_seed { |
| 256 | var full_prefix = [_]u8{ | 214 | const full_prefix = &[_]u8{ |
| 257 | @enumToInt(KeyTypePrefixByte.seed) | (@enumToInt(prefix) >> 5), | 215 | @enumToInt(KeyTypePrefixByte.seed) | (@enumToInt(prefix) >> 5), |
| 258 | (@enumToInt(prefix) & 0b00011111) << 3, | 216 | (@enumToInt(prefix) & 0b00011111) << 3, |
| 259 | }; | 217 | }; |
| 260 | return encode(full_prefix.len, src.len, &full_prefix, src); | 218 | return encode(full_prefix.len, src.len, full_prefix, src); |
| 261 | } | 219 | } |
| 262 | 220 | ||
| 263 | pub fn decodePrivate(text: *const text_private) ![Ed25519.secret_length]u8 { | 221 | pub fn decodePrivate(text: *const text_private) ![Ed25519.secret_length]u8 { |
| 264 | var decoded = try decode(1, Ed25519.secret_length, text); | 222 | var decoded = try decode(1, Ed25519.secret_length, text); |
| 265 | defer wipeBytes(&decoded.data); | 223 | defer decoded.wipe(); |
| 266 | if (decoded.prefix[0] != @enumToInt(KeyTypePrefixByte.private)) | 224 | if (decoded.prefix[0] != @enumToInt(KeyTypePrefixByte.private)) |
| 267 | return error.InvalidPrefixByte; | 225 | return error.InvalidPrefixByte; |
| 268 | return decoded.data; | 226 | return decoded.data; |
| @@ -270,15 +228,23 @@ pub fn decodePrivate(text: *const text_private) ![Ed25519.secret_length]u8 { | |||
| 270 | 228 | ||
| 271 | pub fn decodePublic(prefix: PublicPrefixByte, text: *const text_public) ![Ed25519.public_length]u8 { | 229 | pub fn decodePublic(prefix: PublicPrefixByte, text: *const text_public) ![Ed25519.public_length]u8 { |
| 272 | var decoded = try decode(1, Ed25519.public_length, text); | 230 | var decoded = try decode(1, Ed25519.public_length, text); |
| 231 | defer decoded.wipe(); | ||
| 273 | if (decoded.data[0] != @enumToInt(prefix)) | 232 | if (decoded.data[0] != @enumToInt(prefix)) |
| 274 | return error.InvalidPrefixByte; | 233 | return error.InvalidPrefixByte; |
| 275 | return decoded.data; | 234 | return decoded.data; |
| 276 | } | 235 | } |
| 277 | 236 | ||
| 278 | fn DecodedNKey(comptime prefix_len: usize, comptime data_len: usize) type { | 237 | fn DecodedNkey(comptime prefix_len: usize, comptime data_len: usize) type { |
| 279 | return struct { | 238 | return struct { |
| 239 | const Self = @This(); | ||
| 240 | |||
| 280 | prefix: [prefix_len]u8, | 241 | prefix: [prefix_len]u8, |
| 281 | data: [data_len]u8, | 242 | data: [data_len]u8, |
| 243 | |||
| 244 | pub fn wipe(self: *Self) void { | ||
| 245 | self.prefix[0] = @enumToInt(PublicPrefixByte.account); | ||
| 246 | wipeBytes(&self.data); | ||
| 247 | } | ||
| 282 | }; | 248 | }; |
| 283 | } | 249 | } |
| 284 | 250 | ||
| @@ -286,15 +252,15 @@ fn decode( | |||
| 286 | comptime prefix_len: usize, | 252 | comptime prefix_len: usize, |
| 287 | comptime data_len: usize, | 253 | comptime data_len: usize, |
| 288 | text: *const [base32.Encoder.calcSize(prefix_len + data_len + 2)]u8, | 254 | text: *const [base32.Encoder.calcSize(prefix_len + data_len + 2)]u8, |
| 289 | ) !DecodedNKey(prefix_len, data_len) { | 255 | ) !DecodedNkey(prefix_len, data_len) { |
| 290 | var raw: [prefix_len + data_len + 2]u8 = undefined; | 256 | var raw: [prefix_len + data_len + 2]u8 = undefined; |
| 291 | defer wipeBytes(&raw); | 257 | defer wipeBytes(&raw); |
| 292 | _ = try base32.Decoder.decode(&raw, text[0..]); | 258 | std.debug.assert((try base32.Decoder.decode(&raw, text[0..])).len == raw.len); |
| 293 | 259 | ||
| 294 | var checksum = mem.readIntLittle(u16, raw[raw.len - 2 .. raw.len]); | 260 | var checksum = mem.readIntLittle(u16, raw[raw.len - 2 .. raw.len]); |
| 295 | try crc16.validate(raw[0 .. raw.len - 2], checksum); | 261 | try crc16.validate(raw[0 .. raw.len - 2], checksum); |
| 296 | 262 | ||
| 297 | return DecodedNKey(prefix_len, data_len){ | 263 | return DecodedNkey(prefix_len, data_len){ |
| 298 | .prefix = raw[0..prefix_len].*, | 264 | .prefix = raw[0..prefix_len].*, |
| 299 | .data = raw[prefix_len .. raw.len - 2].*, | 265 | .data = raw[prefix_len .. raw.len - 2].*, |
| 300 | }; | 266 | }; |
| @@ -314,7 +280,7 @@ pub const DecodedSeed = struct { | |||
| 314 | 280 | ||
| 315 | pub fn decodeSeed(text: *const text_seed) !DecodedSeed { | 281 | pub fn decodeSeed(text: *const text_seed) !DecodedSeed { |
| 316 | var decoded = try decode(2, Ed25519.seed_length, text); | 282 | var decoded = try decode(2, Ed25519.seed_length, text); |
| 317 | defer wipeBytes(&decoded.data); // gets copied | 283 | defer decoded.wipe(); // gets copied |
| 318 | 284 | ||
| 319 | var key_ty_prefix = decoded.prefix[0] & 0b11111000; | 285 | var key_ty_prefix = decoded.prefix[0] & 0b11111000; |
| 320 | var entity_ty_prefix = (decoded.prefix[0] & 0b00000111) << 5 | ((decoded.prefix[1] & 0b11111000) >> 3); | 286 | var entity_ty_prefix = (decoded.prefix[0] & 0b00000111) << 5 | ((decoded.prefix[1] & 0b11111000) >> 3); |
| @@ -328,22 +294,6 @@ pub fn decodeSeed(text: *const text_seed) !DecodedSeed { | |||
| 328 | }; | 294 | }; |
| 329 | } | 295 | } |
| 330 | 296 | ||
| 331 | pub fn fromPublicKey(text: *const text_public) !PublicKey { | ||
| 332 | var decoded = try decode(1, Ed25519.public_length, text); | ||
| 333 | defer wipeBytes(&decoded.data); // gets copied | ||
| 334 | |||
| 335 | return PublicKey{ | ||
| 336 | .prefix = try PublicPrefixByte.fromU8(decoded.prefix[0]), | ||
| 337 | .key = decoded.data, | ||
| 338 | }; | ||
| 339 | } | ||
| 340 | |||
| 341 | pub fn fromSeed(text: *const text_seed) !SeedKeyPair { | ||
| 342 | var res = try decodeSeed(text); | ||
| 343 | wipeBytes(&res.seed); | ||
| 344 | return SeedKeyPair{ .seed = text.* }; | ||
| 345 | } | ||
| 346 | |||
| 347 | pub fn isValidEncoding(text: []const u8) bool { | 297 | pub fn isValidEncoding(text: []const u8) bool { |
| 348 | if (text.len < 4) return false; | 298 | if (text.len < 4) return false; |
| 349 | var made_crc: u16 = 0; | 299 | var made_crc: u16 = 0; |
| @@ -367,24 +317,21 @@ pub fn isValidEncoding(text: []const u8) bool { | |||
| 367 | 317 | ||
| 368 | pub fn isValidSeed(text: *const text_seed) bool { | 318 | pub fn isValidSeed(text: *const text_seed) bool { |
| 369 | var res = decodeSeed(text) catch return false; | 319 | var res = decodeSeed(text) catch return false; |
| 370 | wipeBytes(&res.seed); | 320 | res.wipe(); |
| 371 | return true; | 321 | return true; |
| 372 | } | 322 | } |
| 373 | 323 | ||
| 374 | pub fn isValidPublicKey(text: *const text_public, with_type: ?PublicPrefixByte) bool { | 324 | pub fn isValidPublicKey(text: *const text_public, with_type: ?PublicPrefixByte) bool { |
| 375 | var res = decode(1, Ed25519.public_length, text) catch return false; | 325 | var res = decode(1, Ed25519.public_length, text) catch return false; |
| 376 | var public = PublicPrefixByte.fromU8(res.data[0]) catch return false; | 326 | defer res.wipe(); |
| 327 | const public = PublicPrefixByte.fromU8(res.data[0]) catch return false; | ||
| 377 | return if (with_type) |ty| public == ty else true; | 328 | return if (with_type) |ty| public == ty else true; |
| 378 | } | 329 | } |
| 379 | 330 | ||
| 380 | pub fn fromRawSeed(prefix: PublicPrefixByte, raw_seed: *const [Ed25519.seed_length]u8) !SeedKeyPair { | ||
| 381 | return SeedKeyPair{ .seed = try encodeSeed(prefix, raw_seed) }; | ||
| 382 | } | ||
| 383 | |||
| 384 | pub fn getNextLine(text: []const u8, off: *usize) ?[]const u8 { | 331 | pub fn getNextLine(text: []const u8, off: *usize) ?[]const u8 { |
| 385 | if (off.* >= text.len) return null; | 332 | if (off.* >= text.len) return null; |
| 386 | var newline_pos = mem.indexOfPos(u8, text, off.*, "\n") orelse return null; | 333 | const newline_pos = mem.indexOfPos(u8, text, off.*, "\n") orelse return null; |
| 387 | var start = off.*; | 334 | const start = off.*; |
| 388 | var end = newline_pos; | 335 | var end = newline_pos; |
| 389 | if (newline_pos > 0 and text[newline_pos - 1] == '\r') end -= 1; | 336 | if (newline_pos > 0 and text[newline_pos - 1] == '\r') end -= 1; |
| 390 | off.* = newline_pos + 1; | 337 | off.* = newline_pos + 1; |
| @@ -424,13 +371,13 @@ pub fn findKeySection(text: []const u8, off: *usize) ?[]const u8 { | |||
| 424 | 371 | ||
| 425 | // TODO(rutgerbrf): switch to std.mem.SplitIterator | 372 | // TODO(rutgerbrf): switch to std.mem.SplitIterator |
| 426 | while (true) { | 373 | while (true) { |
| 427 | var opening_line = getNextLine(text, off) orelse return null; | 374 | const opening_line = getNextLine(text, off) orelse return null; |
| 428 | if (!isKeySectionBarrier(opening_line)) continue; | 375 | if (!isKeySectionBarrier(opening_line)) continue; |
| 429 | 376 | ||
| 430 | var contents_line = getNextLine(text, off) orelse return null; | 377 | const contents_line = getNextLine(text, off) orelse return null; |
| 431 | if (!areKeySectionContentsValid(contents_line)) continue; | 378 | if (!areKeySectionContentsValid(contents_line)) continue; |
| 432 | 379 | ||
| 433 | var closing_line = getNextLine(text, off) orelse return null; | 380 | const closing_line = getNextLine(text, off) orelse return null; |
| 434 | if (!isKeySectionBarrier(closing_line)) continue; | 381 | if (!isKeySectionBarrier(closing_line)) continue; |
| 435 | 382 | ||
| 436 | return contents_line; | 383 | return contents_line; |
| @@ -442,82 +389,86 @@ pub fn parseDecoratedJwt(contents: []const u8) ![]const u8 { | |||
| 442 | return findKeySection(contents, ¤t_off) orelse return contents; | 389 | return findKeySection(contents, ¤t_off) orelse return contents; |
| 443 | } | 390 | } |
| 444 | 391 | ||
| 445 | fn validNKey(text: []const u8) bool { | 392 | fn validNkey(text: []const u8) bool { |
| 446 | var valid_prefix = | 393 | const valid_prefix = |
| 447 | mem.startsWith(u8, text, "SO") or | 394 | mem.startsWith(u8, text, "SO") or |
| 448 | mem.startsWith(u8, text, "SA") or | 395 | mem.startsWith(u8, text, "SA") or |
| 449 | mem.startsWith(u8, text, "SU"); | 396 | mem.startsWith(u8, text, "SU"); |
| 450 | var valid_len = text.len >= text_seed_len; | 397 | const valid_len = text.len >= text_seed_len; |
| 451 | return valid_prefix and valid_len; | 398 | return valid_prefix and valid_len; |
| 452 | } | 399 | } |
| 453 | 400 | ||
| 454 | fn findNKey(text: []const u8) ?[]const u8 { | 401 | fn findNkey(text: []const u8) ?[]const u8 { |
| 455 | var current_off: usize = 0; | 402 | var current_off: usize = 0; |
| 456 | while (true) { | 403 | while (true) { |
| 457 | var line = getNextLine(text, ¤t_off) orelse return null; | 404 | var line = getNextLine(text, ¤t_off) orelse return null; |
| 458 | for (line) |c, i| { | 405 | for (line) |c, i| { |
| 459 | if (!ascii.isSpace(c)) { | 406 | if (!ascii.isSpace(c)) { |
| 460 | if (validNKey(line[i..])) return line[i..]; | 407 | if (validNkey(line[i..])) return line[i..]; |
| 461 | break; | 408 | break; |
| 462 | } | 409 | } |
| 463 | } | 410 | } |
| 464 | } | 411 | } |
| 465 | } | 412 | } |
| 466 | 413 | ||
| 467 | pub fn parseDecoratedNKey(contents: []const u8) !SeedKeyPair { | 414 | pub fn parseDecoratedNkey(contents: []const u8) !SeedKeyPair { |
| 468 | var current_off: usize = 0; | 415 | var current_off: usize = 0; |
| 469 | |||
| 470 | var seed: ?[]const u8 = null; | 416 | var seed: ?[]const u8 = null; |
| 471 | if (findKeySection(contents, ¤t_off) != null) | 417 | if (findKeySection(contents, ¤t_off) != null) |
| 472 | seed = findKeySection(contents, ¤t_off); | 418 | seed = findKeySection(contents, ¤t_off); |
| 473 | if (seed == null) | 419 | if (seed == null) |
| 474 | seed = findNKey(contents) orelse return error.NoNKeySeedFound; | 420 | seed = findNkey(contents) orelse return error.NoNkeySeedFound; |
| 475 | if (!validNKey(seed.?)) | 421 | if (!validNkey(seed.?)) |
| 476 | return error.NoNKeySeedFound; | 422 | return error.NoNkeySeedFound; |
| 477 | return fromSeed(seed.?[0..text_seed_len]); | 423 | return SeedKeyPair.fromTextSeed(seed.?[0..text_seed_len]); |
| 478 | } | 424 | } |
| 479 | 425 | ||
| 480 | pub fn parseDecoratedUserNKey(contents: []const u8) !SeedKeyPair { | 426 | pub fn parseDecoratedUserNkey(contents: []const u8) !SeedKeyPair { |
| 481 | var key = try parseDecoratedNKey(contents); | 427 | var key = try parseDecoratedNkey(contents); |
| 482 | if (!mem.startsWith(u8, &key.seed, "SU")) return error.NoNKeyUserSeedFound; | 428 | if (!mem.startsWith(u8, &key.seed, "SU")) return error.NoNkeyUserSeedFound; |
| 483 | defer key.wipe(); | 429 | defer key.wipe(); |
| 484 | return key; | 430 | return key; |
| 485 | } | 431 | } |
| 486 | 432 | ||
| 487 | test { | 433 | test { |
| 488 | testing.refAllDecls(@This()); | 434 | testing.refAllDecls(@This()); |
| 489 | testing.refAllDecls(Key); | ||
| 490 | testing.refAllDecls(SeedKeyPair); | 435 | testing.refAllDecls(SeedKeyPair); |
| 491 | testing.refAllDecls(PublicKey); | 436 | testing.refAllDecls(PublicKey); |
| 492 | } | 437 | } |
| 493 | 438 | ||
| 494 | test { | 439 | test { |
| 495 | var key_pair = try SeedKeyPair.init(PublicPrefixByte.server); | 440 | var key_pair = try SeedKeyPair.generate(PublicPrefixByte.server); |
| 496 | defer key_pair.wipe(); | 441 | defer key_pair.wipe(); |
| 497 | 442 | ||
| 498 | var decoded_seed = try decodeSeed(&key_pair.seed); | 443 | var decoded_seed = try decodeSeed(&key_pair.seed); |
| 444 | defer decoded_seed.wipe(); | ||
| 499 | var encoded_second_time = try encodeSeed(decoded_seed.prefix, &decoded_seed.seed); | 445 | var encoded_second_time = try encodeSeed(decoded_seed.prefix, &decoded_seed.seed); |
| 446 | defer wipeBytes(&encoded_second_time); | ||
| 500 | try testing.expectEqualSlices(u8, &key_pair.seed, &encoded_second_time); | 447 | try testing.expectEqualSlices(u8, &key_pair.seed, &encoded_second_time); |
| 501 | try testing.expect(isValidEncoding(&key_pair.seed)); | 448 | try testing.expect(isValidEncoding(&key_pair.seed)); |
| 502 | 449 | ||
| 503 | var pub_key_str_a = try key_pair.publicKey(); | 450 | var pub_key_str_a = try key_pair.publicKey(); |
| 451 | defer wipeBytes(&pub_key_str_a); | ||
| 504 | var priv_key_str = try key_pair.privateKey(); | 452 | var priv_key_str = try key_pair.privateKey(); |
| 453 | defer wipeBytes(&priv_key_str); | ||
| 505 | try testing.expect(pub_key_str_a.len != 0); | 454 | try testing.expect(pub_key_str_a.len != 0); |
| 506 | try testing.expect(priv_key_str.len != 0); | 455 | try testing.expect(priv_key_str.len != 0); |
| 507 | try testing.expect(isValidEncoding(&pub_key_str_a)); | 456 | try testing.expect(isValidEncoding(&pub_key_str_a)); |
| 508 | try testing.expect(isValidEncoding(&priv_key_str)); | 457 | try testing.expect(isValidEncoding(&priv_key_str)); |
| 509 | wipeBytes(&priv_key_str); | ||
| 510 | 458 | ||
| 511 | var pub_key = try key_pair.intoPublicKey(); | 459 | var pub_key = try key_pair.intoPublicKey(); |
| 460 | defer pub_key.wipe(); | ||
| 512 | var pub_key_str_b = try pub_key.publicKey(); | 461 | var pub_key_str_b = try pub_key.publicKey(); |
| 462 | defer wipeBytes(&pub_key_str_b); | ||
| 513 | try testing.expectEqualSlices(u8, &pub_key_str_a, &pub_key_str_b); | 463 | try testing.expectEqualSlices(u8, &pub_key_str_a, &pub_key_str_b); |
| 514 | } | 464 | } |
| 515 | 465 | ||
| 516 | test { | 466 | test { |
| 517 | var creds_bytes = try std.fs.cwd().readFileAlloc(testing.allocator, "fixtures/test.creds", std.math.maxInt(usize)); | 467 | var creds_bytes = try std.fs.cwd().readFileAlloc(testing.allocator, "fixtures/test.creds", std.math.maxInt(usize)); |
| 518 | defer testing.allocator.free(creds_bytes); | 468 | defer testing.allocator.free(creds_bytes); |
| 469 | defer wipeBytes(creds_bytes); | ||
| 519 | 470 | ||
| 520 | // TODO(rutgerbrf): validate the contents of the results of these functions | 471 | // TODO(rutgerbrf): validate the contents of the results of these functions |
| 521 | _ = try parseDecoratedUserNKey(creds_bytes); | 472 | _ = try parseDecoratedUserNkey(creds_bytes); |
| 522 | _ = try parseDecoratedJwt(creds_bytes); | 473 | _ = try parseDecoratedJwt(creds_bytes); |
| 523 | } | 474 | } |
diff --git a/src/znk.zig b/src/znk.zig index ab36c96..fe66cb5 100644 --- a/src/znk.zig +++ b/src/znk.zig | |||
| @@ -162,7 +162,7 @@ pub fn cmdGen(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !voi | |||
| 162 | 162 | ||
| 163 | try PrefixKeyGenerator.init(arena, ty.?, capitalized_prefix).generate(); | 163 | try PrefixKeyGenerator.init(arena, ty.?, capitalized_prefix).generate(); |
| 164 | } else { | 164 | } else { |
| 165 | var kp = nkeys.SeedKeyPair.init(ty.?) catch |e| fatal("could not generate key pair: {e}", .{e}); | 165 | var kp = nkeys.SeedKeyPair.generate(ty.?) catch |e| fatal("could not generate key pair: {e}", .{e}); |
| 166 | defer kp.wipe(); | 166 | defer kp.wipe(); |
| 167 | try stdout.writeAll(&kp.seed); | 167 | try stdout.writeAll(&kp.seed); |
| 168 | try stdout.writeAll("\n"); | 168 | try stdout.writeAll("\n"); |
| @@ -396,7 +396,7 @@ const PrefixKeyGenerator = struct { | |||
| 396 | while (true) { | 396 | while (true) { |
| 397 | if (self.done.load(.SeqCst)) return; | 397 | if (self.done.load(.SeqCst)) return; |
| 398 | 398 | ||
| 399 | var kp = nkeys.SeedKeyPair.init(self.ty) catch |e| fatal("could not generate key pair: {e}", .{e}); | 399 | var kp = nkeys.SeedKeyPair.generate(self.ty) catch |e| fatal("could not generate key pair: {e}", .{e}); |
| 400 | defer kp.wipe(); | 400 | defer kp.wipe(); |
| 401 | var public_key = kp.publicKey() catch |e| fatal("could not generate public key: {e}", .{e}); | 401 | var public_key = kp.publicKey() catch |e| fatal("could not generate public key: {e}", .{e}); |
| 402 | if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue; | 402 | if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue; |
| @@ -425,22 +425,6 @@ const PrefixKeyGenerator = struct { | |||
| 425 | }; | 425 | }; |
| 426 | }; | 426 | }; |
| 427 | 427 | ||
| 428 | pub fn readKeyFile(allocator: *Allocator, file: fs.File) nkeys.Key { | ||
| 429 | var bytes = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch fatal("could not read key file", .{}); | ||
| 430 | |||
| 431 | var iterator = mem.split(bytes, "\n"); | ||
| 432 | while (iterator.next()) |line| { | ||
| 433 | if (nkeys.isValidEncoding(line) and line.len == nkeys.text_seed_len) { | ||
| 434 | var k = nkeys.fromText(line) catch continue; | ||
| 435 | defer k.wipe(); | ||
| 436 | allocator.free(bytes); | ||
| 437 | return k; | ||
| 438 | } | ||
| 439 | } | ||
| 440 | |||
| 441 | fatal("could not find a valid key", .{}); | ||
| 442 | } | ||
| 443 | |||
| 444 | fn two(slice: []const bool) bool { | 428 | fn two(slice: []const bool) bool { |
| 445 | var one = false; | 429 | var one = false; |
| 446 | for (slice) |x| if (x and one) { | 430 | for (slice) |x| if (x and one) { |
| @@ -457,6 +441,79 @@ fn toUpper(allocator: *Allocator, slice: []const u8) ![]u8 { | |||
| 457 | return result; | 441 | return result; |
| 458 | } | 442 | } |
| 459 | 443 | ||
| 444 | pub const Nkey = union(enum) { | ||
| 445 | seed_key_pair: nkeys.SeedKeyPair, | ||
| 446 | public_key: nkeys.PublicKey, | ||
| 447 | |||
| 448 | const Self = @This(); | ||
| 449 | |||
| 450 | pub fn publicKey(self: *const Self) !nkeys.text_public { | ||
| 451 | return switch (self.*) { | ||
| 452 | .seed_key_pair => |*kp| try kp.publicKey(), | ||
| 453 | .public_key => |*pk| try pk.publicKey(), | ||
| 454 | }; | ||
| 455 | } | ||
| 456 | |||
| 457 | pub fn intoPublicKey(self: *const Self) !nkeys.PublicKey { | ||
| 458 | return switch (self.*) { | ||
| 459 | .seed_key_pair => |*kp| try kp.intoPublicKey(), | ||
| 460 | .public_key => |pk| pk, | ||
| 461 | }; | ||
| 462 | } | ||
| 463 | |||
| 464 | pub fn verify( | ||
| 465 | self: *const Self, | ||
| 466 | msg: []const u8, | ||
| 467 | sig: [std.crypto.sign.Ed25519.signature_length]u8, | ||
| 468 | ) !void { | ||
| 469 | return switch (self.*) { | ||
| 470 | .seed_key_pair => |*kp| try kp.verify(msg, sig), | ||
| 471 | .public_key => |*pk| try pk.verify(msg, sig), | ||
| 472 | }; | ||
| 473 | } | ||
| 474 | |||
| 475 | pub fn wipe(self: *Self) void { | ||
| 476 | return switch (self.*) { | ||
| 477 | .seed_key_pair => |*kp| kp.wipe(), | ||
| 478 | .public_key => |*pk| pk.wipe(), | ||
| 479 | }; | ||
| 480 | } | ||
| 481 | |||
| 482 | pub fn fromText(text: []const u8) !Self { | ||
| 483 | if (!nkeys.isValidEncoding(text)) return error.InvalidEncoding; | ||
| 484 | switch (text[0]) { | ||
| 485 | 'S' => { | ||
| 486 | // It's a seed. | ||
| 487 | if (text.len != nkeys.text_seed_len) return error.InvalidSeed; | ||
| 488 | return Self{ .seed_key_pair = try nkeys.SeedKeyPair.fromTextSeed(text[0..nkeys.text_seed_len]) }; | ||
| 489 | }, | ||
| 490 | 'P' => return error.InvalidEncoding, // unsupported for now | ||
| 491 | else => { | ||
| 492 | if (text.len != nkeys.text_public_len) return error.InvalidEncoding; | ||
| 493 | return Self{ .public_key = try nkeys.PublicKey.fromTextPublicKey(text[0..nkeys.text_public_len]) }; | ||
| 494 | }, | ||
| 495 | } | ||
| 496 | } | ||
| 497 | }; | ||
| 498 | |||
| 499 | pub fn readKeyFile(allocator: *Allocator, file: fs.File) Nkey { | ||
| 500 | var bytes = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch fatal("could not read key file", .{}); | ||
| 501 | |||
| 502 | var iterator = mem.split(bytes, "\n"); | ||
| 503 | while (iterator.next()) |line| { | ||
| 504 | if (nkeys.isValidEncoding(line) and line.len == nkeys.text_seed_len) { | ||
| 505 | var k = Nkey.fromText(line) catch continue; | ||
| 506 | defer k.wipe(); | ||
| 507 | allocator.free(bytes); | ||
| 508 | return k; | ||
| 509 | } | ||
| 510 | } | ||
| 511 | |||
| 512 | fatal("could not find a valid key", .{}); | ||
| 513 | } | ||
| 514 | |||
| 460 | test { | 515 | test { |
| 461 | testing.refAllDecls(@This()); | 516 | testing.refAllDecls(@This()); |
| 517 | testing.refAllDecls(Nkey); | ||
| 518 | testing.refAllDecls(PrefixKeyGenerator); | ||
| 462 | } | 519 | } |