| ... | @@ -13,6 +13,16 @@ pub const Key = struct { | ... | @@ -13,6 +13,16 @@ pub const Key = struct { |
| 13 | subject_end: u32, | 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 | /// The returned bytes become invalid after calling any of the rescan functions | 26 | /// The returned bytes become invalid after calling any of the rescan functions |
| 17 | /// or add functions. | 27 | /// or add functions. |
| 18 | pub fn find(cb: CertificateBundle, subject_name: []const u8) ?u32 { | 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,18 +130,11 @@ pub fn key(cb: CertificateBundle, bytes_index: u32) !Key { |
| 120 | const tbs_certificate = try Der.parseElement(bytes, certificate.start); | 130 | const tbs_certificate = try Der.parseElement(bytes, certificate.start); |
| 121 | const version = try Der.parseElement(bytes, tbs_certificate.start); | 131 | const version = try Der.parseElement(bytes, tbs_certificate.start); |
| 122 | try checkVersion(bytes, version); | 132 | try checkVersion(bytes, version); |
| 123 | | | |
| 124 | const serial_number = try Der.parseElement(bytes, version.end); | 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 | const signature = try Der.parseElement(bytes, serial_number.end); | 134 | const signature = try Der.parseElement(bytes, serial_number.end); |
| 130 | const issuer = try Der.parseElement(bytes, signature.end); | 135 | const issuer = try Der.parseElement(bytes, signature.end); |
| 131 | const validity = try Der.parseElement(bytes, issuer.end); | 136 | const validity = try Der.parseElement(bytes, issuer.end); |
| 132 | const subject = try Der.parseElement(bytes, validity.end); | 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 | return .{ | 139 | return .{ |
| 137 | .subject_start = subject.start, | 140 | .subject_start = subject.start, |
| ... | @@ -143,70 +146,163 @@ pub const Certificate = struct { | ... | @@ -143,70 +146,163 @@ pub const Certificate = struct { |
| 143 | buffer: []const u8, | 146 | buffer: []const u8, |
| 144 | index: u32, | 147 | index: u32, |
| 145 | | 148 | |
| 146 | pub fn verify(subject: Certificate, issuer: Certificate) !void { | 149 | pub const Algorithm = enum { |
| 147 | const subject_certificate = try Der.parseElement(subject.buffer, subject.index); | 150 | sha1WithRSAEncryption, |
| 148 | const subject_tbs_certificate = try Der.parseElement(subject.buffer, subject_certificate.start); | 151 | sha224WithRSAEncryption, |
| 149 | const subject_version = try Der.parseElement(subject.buffer, subject_tbs_certificate.start); | 152 | sha256WithRSAEncryption, |
| 150 | try checkVersion(subject.buffer, subject_version); | 153 | sha384WithRSAEncryption, |
| 151 | const subject_serial_number = try Der.parseElement(subject.buffer, subject_version.end); | 154 | sha512WithRSAEncryption, |
| 152 | // RFC 5280, section 4.1.2.3: | 155 | |
| 153 | // "This field MUST contain the same algorithm identifier as | 156 | pub const map = std.ComptimeStringMap(Algorithm, .{ |
| 154 | // the signatureAlgorithm field in the sequence Certificate." | 157 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption }, |
| 155 | const subject_signature = try Der.parseElement(subject.buffer, subject_serial_number.end); | 158 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption }, |
| 156 | const subject_issuer = try Der.parseElement(subject.buffer, subject_signature.end); | 159 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption }, |
| 157 | const subject_validity = try Der.parseElement(subject.buffer, subject_issuer.end); | 160 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption }, |
| 158 | //const subject_name = try Der.parseElement(subject.buffer, subject_validity.end); | 161 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption }, |
| 159 | | 162 | }); |
| 160 | const subject_sig_algo = try Der.parseElement(subject.buffer, subject_tbs_certificate.end); | 163 | |
| 161 | const subject_algo_elem = try Der.parseElement(subject.buffer, subject_sig_algo.start); | 164 | pub fn Hash(comptime algorithm: Algorithm) type { |
| 162 | const subject_algo = try Der.parseObjectId(subject.buffer, subject_algo_elem); | 165 | return switch (algorithm) { |
| 163 | const subject_sig_elem = try Der.parseElement(subject.buffer, subject_sig_algo.end); | 166 | .sha1WithRSAEncryption => crypto.hash.Sha1, |
| 164 | const subject_sig = try parseBitString(subject, subject_sig_elem); | 167 | .sha224WithRSAEncryption => crypto.hash.sha2.Sha224, |
| 165 | | 168 | .sha256WithRSAEncryption => crypto.hash.sha2.Sha256, |
| 166 | const issuer_certificate = try Der.parseElement(issuer.buffer, issuer.index); | 169 | .sha384WithRSAEncryption => crypto.hash.sha2.Sha384, |
| 167 | const issuer_tbs_certificate = try Der.parseElement(issuer.buffer, issuer_certificate.start); | 170 | .sha512WithRSAEncryption => crypto.hash.sha2.Sha512, |
| 168 | const issuer_version = try Der.parseElement(issuer.buffer, issuer_tbs_certificate.start); | 171 | }; |
| 169 | try checkVersion(issuer.buffer, issuer_version); | 172 | } |
| 170 | const issuer_serial_number = try Der.parseElement(issuer.buffer, issuer_version.end); | 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 | // RFC 5280, section 4.1.2.3: | 252 | // RFC 5280, section 4.1.2.3: |
| 172 | // "This field MUST contain the same algorithm identifier as | 253 | // "This field MUST contain the same algorithm identifier as |
| 173 | // the signatureAlgorithm field in the sequence Certificate." | 254 | // the signatureAlgorithm field in the sequence Certificate." |
| 174 | const issuer_signature = try Der.parseElement(issuer.buffer, issuer_serial_number.end); | 255 | const tbs_signature = try Der.parseElement(cert_bytes, serial_number.end); |
| 175 | const issuer_issuer = try Der.parseElement(issuer.buffer, issuer_signature.end); | 256 | const issuer = try Der.parseElement(cert_bytes, tbs_signature.end); |
| 176 | const issuer_validity = try Der.parseElement(issuer.buffer, issuer_issuer.end); | 257 | const validity = try Der.parseElement(cert_bytes, issuer.end); |
| 177 | const issuer_name = try Der.parseElement(issuer.buffer, issuer_validity.end); | 258 | const subject = try Der.parseElement(cert_bytes, validity.end); |
| 178 | const issuer_pub_key_info = try Der.parseElement(issuer.buffer, issuer_name.end); | 259 | |
| 179 | const issuer_pub_key_signature_algorithm = try Der.parseElement(issuer.buffer, issuer_pub_key_info.start); | 260 | const pub_key_info = try Der.parseElement(cert_bytes, subject.end); |
| 180 | const issuer_pub_key_algo_elem = try Der.parseElement(issuer.buffer, issuer_pub_key_signature_algorithm.start); | 261 | const pub_key_signature_algorithm = try Der.parseElement(cert_bytes, pub_key_info.start); |
| 181 | const issuer_pub_key_algo = try Der.parseObjectId(issuer.buffer, issuer_pub_key_algo_elem); | 262 | const pub_key_algo_elem = try Der.parseElement(cert_bytes, pub_key_signature_algorithm.start); |
| 182 | const issuer_pub_key_elem = try Der.parseElement(issuer.buffer, issuer_pub_key_signature_algorithm.end); | 263 | const pub_key_algo = try parseAlgorithmCategory(cert_bytes, pub_key_algo_elem); |
| 183 | const issuer_pub_key = try parseBitString(issuer, issuer_pub_key_elem); | 264 | const pub_key_elem = try Der.parseElement(cert_bytes, pub_key_signature_algorithm.end); |
| 184 | | 265 | const pub_key = try parseBitString(cert, pub_key_elem); |
| 185 | // Check that the subject's issuer name matches the issuer's subject | 266 | |
| 186 | // name. | 267 | const rdn = try Der.parseElement(cert_bytes, subject.start); |
| 187 | if (!mem.eql(u8, subject.contents(subject_issuer), issuer.contents(issuer_name))) { | 268 | const atav = try Der.parseElement(cert_bytes, rdn.start); |
| 188 | return error.CertificateIssuerMismatch; | 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 | 283 | const sig_algo = try Der.parseElement(cert_bytes, tbs_certificate.end); |
| 192 | _ = subject_validity; | 284 | const algo_elem = try Der.parseElement(cert_bytes, sig_algo.start); |
| 193 | // TODO check the time validity for the issuer | 285 | const signature_algorithm = try parseAlgorithm(cert_bytes, algo_elem); |
| 194 | | 286 | const sig_elem = try Der.parseElement(cert_bytes, sig_algo.end); |
| 195 | const message = subject.buffer[subject_certificate.start..subject_tbs_certificate.end]; | 287 | const signature = try parseBitString(cert, sig_elem); |
| 196 | //std.debug.print("issuer algo: {any} subject algo: {any}\n", .{ issuer_pub_key_algo, subject_algo }); | 288 | |
| 197 | switch (subject_algo) { | 289 | return .{ |
| 198 | // zig fmt: off | 290 | .certificate = cert, |
| 199 | .sha1WithRSAEncryption => return verifyRsa(crypto.hash.Sha1, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), | 291 | .common_name = common_name, |
| 200 | .sha224WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha224, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), | 292 | .issuer = cert.contents(issuer), |
| 201 | .sha256WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha256, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), | 293 | .subject = cert.contents(subject), |
| 202 | .sha384WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha384, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), | 294 | .signature = signature, |
| 203 | .sha512WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha512, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), | 295 | .signature_algorithm = signature_algorithm, |
| 204 | // zig fmt: on | 296 | .message = cert_bytes[certificate.start..tbs_certificate.end], |
| 205 | else => { | 297 | .pub_key_algo = pub_key_algo, |
| 206 | std.debug.print("unhandled algorithm: {any}\n", .{subject_algo}); | 298 | .pub_key = pub_key, |
| 207 | return error.UnsupportedCertificateSignatureAlgorithm; | 299 | }; |
| 208 | }, | 300 | } |
| 209 | } | 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 | pub fn contents(cert: Certificate, elem: Der.Element) []const u8 { | 308 | pub fn contents(cert: Certificate, elem: Der.Element) []const u8 { |
| ... | @@ -219,7 +315,30 @@ pub const Certificate = struct { | ... | @@ -219,7 +315,30 @@ pub const Certificate = struct { |
| 219 | return cert.buffer[elem.start + 1 .. elem.end]; | 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 | if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch; | 342 | if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch; |
| 224 | const pub_key_seq = try Der.parseElement(pub_key, 0); | 343 | const pub_key_seq = try Der.parseElement(pub_key, 0); |
| 225 | if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType; | 344 | if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType; |