| ... | ... | @@ -13,6 +13,16 @@ pub const Key = struct { |
| 13 | 13 | subject_end: u32, |
| 14 | 14 | }; |
| 15 | 15 | |
| 16 | pub fn verify(cb: CertificateBundle, subject: Certificate.Parsed) !void { |
| 17 | const bytes_index = cb.find(subject.issuer) orelse return error.IssuerNotFound; |
| 18 | const issuer_cert: Certificate = .{ |
| 19 | .buffer = cb.bytes.items, |
| 20 | .index = bytes_index, |
| 21 | }; |
| 22 | const issuer = try issuer_cert.parse(); |
| 23 | try subject.verify(issuer); |
| 24 | } |
| 25 | |
| 16 | 26 | /// The returned bytes become invalid after calling any of the rescan functions |
| 17 | 27 | /// or add functions. |
| 18 | 28 | pub fn find(cb: CertificateBundle, subject_name: []const u8) ?u32 { |
| ... | ... | @@ -120,18 +130,11 @@ pub fn key(cb: CertificateBundle, bytes_index: u32) !Key { |
| 120 | 130 | const tbs_certificate = try Der.parseElement(bytes, certificate.start); |
| 121 | 131 | const version = try Der.parseElement(bytes, tbs_certificate.start); |
| 122 | 132 | try checkVersion(bytes, version); |
| 123 | | |
| 124 | 133 | const serial_number = try Der.parseElement(bytes, version.end); |
| 125 | | |
| 126 | | // RFC 5280, section 4.1.2.3: |
| 127 | | // "This field MUST contain the same algorithm identifier as |
| 128 | | // the signatureAlgorithm field in the sequence Certificate." |
| 129 | 134 | const signature = try Der.parseElement(bytes, serial_number.end); |
| 130 | 135 | const issuer = try Der.parseElement(bytes, signature.end); |
| 131 | 136 | const validity = try Der.parseElement(bytes, issuer.end); |
| 132 | 137 | const subject = try Der.parseElement(bytes, validity.end); |
| 133 | | //const subject_pub_key = try Der.parseElement(bytes, subject.end); |
| 134 | | //const extensions = try Der.parseElement(bytes, subject_pub_key.end); |
| 135 | 138 | |
| 136 | 139 | return .{ |
| 137 | 140 | .subject_start = subject.start, |
| ... | ... | @@ -143,70 +146,163 @@ pub const Certificate = struct { |
| 143 | 146 | buffer: []const u8, |
| 144 | 147 | index: u32, |
| 145 | 148 | |
| 146 | | pub fn verify(subject: Certificate, issuer: Certificate) !void { |
| 147 | | const subject_certificate = try Der.parseElement(subject.buffer, subject.index); |
| 148 | | const subject_tbs_certificate = try Der.parseElement(subject.buffer, subject_certificate.start); |
| 149 | | const subject_version = try Der.parseElement(subject.buffer, subject_tbs_certificate.start); |
| 150 | | try checkVersion(subject.buffer, subject_version); |
| 151 | | const subject_serial_number = try Der.parseElement(subject.buffer, subject_version.end); |
| 152 | | // RFC 5280, section 4.1.2.3: |
| 153 | | // "This field MUST contain the same algorithm identifier as |
| 154 | | // the signatureAlgorithm field in the sequence Certificate." |
| 155 | | const subject_signature = try Der.parseElement(subject.buffer, subject_serial_number.end); |
| 156 | | const subject_issuer = try Der.parseElement(subject.buffer, subject_signature.end); |
| 157 | | const subject_validity = try Der.parseElement(subject.buffer, subject_issuer.end); |
| 158 | | //const subject_name = try Der.parseElement(subject.buffer, subject_validity.end); |
| 159 | | |
| 160 | | const subject_sig_algo = try Der.parseElement(subject.buffer, subject_tbs_certificate.end); |
| 161 | | const subject_algo_elem = try Der.parseElement(subject.buffer, subject_sig_algo.start); |
| 162 | | const subject_algo = try Der.parseObjectId(subject.buffer, subject_algo_elem); |
| 163 | | const subject_sig_elem = try Der.parseElement(subject.buffer, subject_sig_algo.end); |
| 164 | | const subject_sig = try parseBitString(subject, subject_sig_elem); |
| 165 | | |
| 166 | | const issuer_certificate = try Der.parseElement(issuer.buffer, issuer.index); |
| 167 | | const issuer_tbs_certificate = try Der.parseElement(issuer.buffer, issuer_certificate.start); |
| 168 | | const issuer_version = try Der.parseElement(issuer.buffer, issuer_tbs_certificate.start); |
| 169 | | try checkVersion(issuer.buffer, issuer_version); |
| 170 | | const issuer_serial_number = try Der.parseElement(issuer.buffer, issuer_version.end); |
| 149 | pub const Algorithm = enum { |
| 150 | sha1WithRSAEncryption, |
| 151 | sha224WithRSAEncryption, |
| 152 | sha256WithRSAEncryption, |
| 153 | sha384WithRSAEncryption, |
| 154 | sha512WithRSAEncryption, |
| 155 | |
| 156 | pub const map = std.ComptimeStringMap(Algorithm, .{ |
| 157 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption }, |
| 158 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption }, |
| 159 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption }, |
| 160 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption }, |
| 161 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption }, |
| 162 | }); |
| 163 | |
| 164 | pub fn Hash(comptime algorithm: Algorithm) type { |
| 165 | return switch (algorithm) { |
| 166 | .sha1WithRSAEncryption => crypto.hash.Sha1, |
| 167 | .sha224WithRSAEncryption => crypto.hash.sha2.Sha224, |
| 168 | .sha256WithRSAEncryption => crypto.hash.sha2.Sha256, |
| 169 | .sha384WithRSAEncryption => crypto.hash.sha2.Sha384, |
| 170 | .sha512WithRSAEncryption => crypto.hash.sha2.Sha512, |
| 171 | }; |
| 172 | } |
| 173 | }; |
| 174 | |
| 175 | pub const AlgorithmCategory = enum { |
| 176 | rsaEncryption, |
| 177 | X9_62_id_ecPublicKey, |
| 178 | |
| 179 | pub const map = std.ComptimeStringMap(AlgorithmCategory, .{ |
| 180 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption }, |
| 181 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey }, |
| 182 | }); |
| 183 | }; |
| 184 | |
| 185 | pub const Attribute = enum { |
| 186 | commonName, |
| 187 | serialNumber, |
| 188 | countryName, |
| 189 | localityName, |
| 190 | stateOrProvinceName, |
| 191 | organizationName, |
| 192 | organizationalUnitName, |
| 193 | organizationIdentifier, |
| 194 | |
| 195 | pub const map = std.ComptimeStringMap(Attribute, .{ |
| 196 | .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName }, |
| 197 | .{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber }, |
| 198 | .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName }, |
| 199 | .{ &[_]u8{ 0x55, 0x04, 0x07 }, .localityName }, |
| 200 | .{ &[_]u8{ 0x55, 0x04, 0x08 }, .stateOrProvinceName }, |
| 201 | .{ &[_]u8{ 0x55, 0x04, 0x0A }, .organizationName }, |
| 202 | .{ &[_]u8{ 0x55, 0x04, 0x0B }, .organizationalUnitName }, |
| 203 | .{ &[_]u8{ 0x55, 0x04, 0x61 }, .organizationIdentifier }, |
| 204 | }); |
| 205 | }; |
| 206 | |
| 207 | pub const Parsed = struct { |
| 208 | certificate: Certificate, |
| 209 | issuer: []const u8, |
| 210 | subject: []const u8, |
| 211 | common_name: []const u8, |
| 212 | signature: []const u8, |
| 213 | signature_algorithm: Algorithm, |
| 214 | message: []const u8, |
| 215 | pub_key_algo: AlgorithmCategory, |
| 216 | pub_key: []const u8, |
| 217 | |
| 218 | pub fn verify(subject: Parsed, issuer: Parsed) !void { |
| 219 | // Check that the subject's issuer name matches the issuer's |
| 220 | // subject name. |
| 221 | if (!mem.eql(u8, subject.issuer, issuer.subject)) { |
| 222 | return error.CertificateIssuerMismatch; |
| 223 | } |
| 224 | |
| 225 | // TODO check the time validity for the subject |
| 226 | // TODO check the time validity for the issuer |
| 227 | |
| 228 | switch (subject.signature_algorithm) { |
| 229 | inline .sha1WithRSAEncryption, |
| 230 | .sha224WithRSAEncryption, |
| 231 | .sha256WithRSAEncryption, |
| 232 | .sha384WithRSAEncryption, |
| 233 | .sha512WithRSAEncryption, |
| 234 | => |algorithm| return verifyRsa( |
| 235 | algorithm.Hash(), |
| 236 | subject.message, |
| 237 | subject.signature, |
| 238 | issuer.pub_key_algo, |
| 239 | issuer.pub_key, |
| 240 | ), |
| 241 | } |
| 242 | } |
| 243 | }; |
| 244 | |
| 245 | pub fn parse(cert: Certificate) !Parsed { |
| 246 | const cert_bytes = cert.buffer; |
| 247 | const certificate = try Der.parseElement(cert_bytes, cert.index); |
| 248 | const tbs_certificate = try Der.parseElement(cert_bytes, certificate.start); |
| 249 | const version = try Der.parseElement(cert_bytes, tbs_certificate.start); |
| 250 | try checkVersion(cert_bytes, version); |
| 251 | const serial_number = try Der.parseElement(cert_bytes, version.end); |
| 171 | 252 | // RFC 5280, section 4.1.2.3: |
| 172 | 253 | // "This field MUST contain the same algorithm identifier as |
| 173 | 254 | // the signatureAlgorithm field in the sequence Certificate." |
| 174 | | const issuer_signature = try Der.parseElement(issuer.buffer, issuer_serial_number.end); |
| 175 | | const issuer_issuer = try Der.parseElement(issuer.buffer, issuer_signature.end); |
| 176 | | const issuer_validity = try Der.parseElement(issuer.buffer, issuer_issuer.end); |
| 177 | | const issuer_name = try Der.parseElement(issuer.buffer, issuer_validity.end); |
| 178 | | const issuer_pub_key_info = try Der.parseElement(issuer.buffer, issuer_name.end); |
| 179 | | const issuer_pub_key_signature_algorithm = try Der.parseElement(issuer.buffer, issuer_pub_key_info.start); |
| 180 | | const issuer_pub_key_algo_elem = try Der.parseElement(issuer.buffer, issuer_pub_key_signature_algorithm.start); |
| 181 | | const issuer_pub_key_algo = try Der.parseObjectId(issuer.buffer, issuer_pub_key_algo_elem); |
| 182 | | const issuer_pub_key_elem = try Der.parseElement(issuer.buffer, issuer_pub_key_signature_algorithm.end); |
| 183 | | const issuer_pub_key = try parseBitString(issuer, issuer_pub_key_elem); |
| 184 | | |
| 185 | | // Check that the subject's issuer name matches the issuer's subject |
| 186 | | // name. |
| 187 | | if (!mem.eql(u8, subject.contents(subject_issuer), issuer.contents(issuer_name))) { |
| 188 | | return error.CertificateIssuerMismatch; |
| 255 | const tbs_signature = try Der.parseElement(cert_bytes, serial_number.end); |
| 256 | const issuer = try Der.parseElement(cert_bytes, tbs_signature.end); |
| 257 | const validity = try Der.parseElement(cert_bytes, issuer.end); |
| 258 | const subject = try Der.parseElement(cert_bytes, validity.end); |
| 259 | |
| 260 | const pub_key_info = try Der.parseElement(cert_bytes, subject.end); |
| 261 | const pub_key_signature_algorithm = try Der.parseElement(cert_bytes, pub_key_info.start); |
| 262 | const pub_key_algo_elem = try Der.parseElement(cert_bytes, pub_key_signature_algorithm.start); |
| 263 | const pub_key_algo = try parseAlgorithmCategory(cert_bytes, pub_key_algo_elem); |
| 264 | const pub_key_elem = try Der.parseElement(cert_bytes, pub_key_signature_algorithm.end); |
| 265 | const pub_key = try parseBitString(cert, pub_key_elem); |
| 266 | |
| 267 | const rdn = try Der.parseElement(cert_bytes, subject.start); |
| 268 | const atav = try Der.parseElement(cert_bytes, rdn.start); |
| 269 | |
| 270 | var common_name: []const u8 = &.{}; |
| 271 | var atav_i = atav.start; |
| 272 | while (atav_i < atav.end) { |
| 273 | const ty_elem = try Der.parseElement(cert_bytes, atav_i); |
| 274 | const ty = try parseAttribute(cert_bytes, ty_elem); |
| 275 | const val = try Der.parseElement(cert_bytes, ty_elem.end); |
| 276 | switch (ty) { |
| 277 | .commonName => common_name = cert.contents(val), |
| 278 | else => {}, |
| 279 | } |
| 280 | atav_i = val.end; |
| 189 | 281 | } |
| 190 | 282 | |
| 191 | | // TODO check the time validity for the subject |
| 192 | | _ = subject_validity; |
| 193 | | // TODO check the time validity for the issuer |
| 194 | | |
| 195 | | const message = subject.buffer[subject_certificate.start..subject_tbs_certificate.end]; |
| 196 | | //std.debug.print("issuer algo: {any} subject algo: {any}\n", .{ issuer_pub_key_algo, subject_algo }); |
| 197 | | switch (subject_algo) { |
| 198 | | // zig fmt: off |
| 199 | | .sha1WithRSAEncryption => return verifyRsa(crypto.hash.Sha1, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), |
| 200 | | .sha224WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha224, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), |
| 201 | | .sha256WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha256, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), |
| 202 | | .sha384WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha384, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), |
| 203 | | .sha512WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha512, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), |
| 204 | | // zig fmt: on |
| 205 | | else => { |
| 206 | | std.debug.print("unhandled algorithm: {any}\n", .{subject_algo}); |
| 207 | | return error.UnsupportedCertificateSignatureAlgorithm; |
| 208 | | }, |
| 209 | | } |
| 283 | const sig_algo = try Der.parseElement(cert_bytes, tbs_certificate.end); |
| 284 | const algo_elem = try Der.parseElement(cert_bytes, sig_algo.start); |
| 285 | const signature_algorithm = try parseAlgorithm(cert_bytes, algo_elem); |
| 286 | const sig_elem = try Der.parseElement(cert_bytes, sig_algo.end); |
| 287 | const signature = try parseBitString(cert, sig_elem); |
| 288 | |
| 289 | return .{ |
| 290 | .certificate = cert, |
| 291 | .common_name = common_name, |
| 292 | .issuer = cert.contents(issuer), |
| 293 | .subject = cert.contents(subject), |
| 294 | .signature = signature, |
| 295 | .signature_algorithm = signature_algorithm, |
| 296 | .message = cert_bytes[certificate.start..tbs_certificate.end], |
| 297 | .pub_key_algo = pub_key_algo, |
| 298 | .pub_key = pub_key, |
| 299 | }; |
| 300 | } |
| 301 | |
| 302 | pub fn verify(subject: Certificate, issuer: Certificate) !void { |
| 303 | const parsed_subject = try subject.parse(); |
| 304 | const parsed_issuer = try issuer.parse(); |
| 305 | return parsed_subject.verify(parsed_issuer); |
| 210 | 306 | } |
| 211 | 307 | |
| 212 | 308 | pub fn contents(cert: Certificate, elem: Der.Element) []const u8 { |
| ... | ... | @@ -219,7 +315,30 @@ pub const Certificate = struct { |
| 219 | 315 | return cert.buffer[elem.start + 1 .. elem.end]; |
| 220 | 316 | } |
| 221 | 317 | |
| 222 | | fn verifyRsa(comptime Hash: type, message: []const u8, sig: []const u8, pub_key_algo: Der.Oid, pub_key: []const u8) !void { |
| 318 | pub fn parseAlgorithm(bytes: []const u8, element: Der.Element) !Algorithm { |
| 319 | if (element.identifier.tag != .object_identifier) |
| 320 | return error.CertificateFieldHasWrongDataType; |
| 321 | return Algorithm.map.get(bytes[element.start..element.end]) orelse |
| 322 | return error.CertificateHasUnrecognizedAlgorithm; |
| 323 | } |
| 324 | |
| 325 | pub fn parseAlgorithmCategory(bytes: []const u8, element: Der.Element) !AlgorithmCategory { |
| 326 | if (element.identifier.tag != .object_identifier) |
| 327 | return error.CertificateFieldHasWrongDataType; |
| 328 | return AlgorithmCategory.map.get(bytes[element.start..element.end]) orelse { |
| 329 | std.debug.print("unrecognized algorithm category: {}\n", .{std.fmt.fmtSliceHexLower(bytes[element.start..element.end])}); |
| 330 | return error.CertificateHasUnrecognizedAlgorithmCategory; |
| 331 | }; |
| 332 | } |
| 333 | |
| 334 | pub fn parseAttribute(bytes: []const u8, element: Der.Element) !Attribute { |
| 335 | if (element.identifier.tag != .object_identifier) |
| 336 | return error.CertificateFieldHasWrongDataType; |
| 337 | return Attribute.map.get(bytes[element.start..element.end]) orelse |
| 338 | return error.CertificateHasUnrecognizedAlgorithm; |
| 339 | } |
| 340 | |
| 341 | fn verifyRsa(comptime Hash: type, message: []const u8, sig: []const u8, pub_key_algo: AlgorithmCategory, pub_key: []const u8) !void { |
| 223 | 342 | if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch; |
| 224 | 343 | const pub_key_seq = try Der.parseElement(pub_key, 0); |
| 225 | 344 | if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType; |