diff --git a/ext/.gitignore b/ext/.gitignore index 836cdc2..41301d3 100644 --- a/ext/.gitignore +++ b/ext/.gitignore @@ -4,3 +4,4 @@ Makefile* *.so asn1c/ +sha1collisiondetection/ diff --git a/ext/README b/ext/README index 1285dc7..9a48db4 100644 --- a/ext/README +++ b/ext/README @@ -10,6 +10,9 @@ cd .. asn1c/asn1c/asn1c -S asn1c/skeletons -pdu=all -pdu=Certificate -fwide-types *.asn1 rm converter-sample.c +git clone https://github.com/cr-marcstevens/sha1collisiondetection.git +cp sha1collisiondetection/lib/* . + # RFC3280 has a Time type which will cause the compiler to create a Time.h # file. This will conflict with on a case insensitive filesystem. # You can work around this problem with this hack: diff --git a/ext/asn1validator.c b/ext/certlint_ext.c similarity index 84% rename from ext/asn1validator.c rename to ext/certlint_ext.c index b7fc4e2..f473e14 100644 --- a/ext/asn1validator.c +++ b/ext/certlint_ext.c @@ -1,6 +1,9 @@ /* - * Ruby bindings for interfacing with asn1c-generated ASN.1 PDU parsing code. - * Copyright (c) 2016 Matt Palmer + * Ruby bindings for a variety of C libraries used for various parts of + * certlint's work. See README in this directory for build instructions and + * references to the library code used. + * + * Copyright (c) 2016,2017 Matt Palmer * * Licensed under the Apache License, Version 2.0 (the "License"). You may not * use this file except in compliance with the License. A copy of the License @@ -21,6 +24,7 @@ #include #include +#include "sha1collisiondetection/lib/sha1.h" extern asn_TYPE_descriptor_t *asn_pdu_collection[]; @@ -36,7 +40,7 @@ static asn_TYPE_descriptor_t *asn1pdu_type_descriptor(VALUE pdu_type) { } pdu++; } - + rb_raise(rb_eArgError, "Unknown PDU type"); return NULL; /* Unreachable, we hope */ } @@ -110,13 +114,13 @@ static VALUE asn1pdu_to_der(VALUE self) { VALUE t = rb_iv_get(self, "@pdu_type"); void *pdu_structure = asn1pdu_decode_pdu(d, t); asn_enc_rval_t rv; - + rv.encoded = -1; while (rv.encoded == -1) { if (derbuf) free(derbuf); derlen *= 2; - + derbuf = malloc(derlen); if (derbuf == NULL) { rb_raise(rb_eNoMemError, "Unable to allocate memory for derbuf"); @@ -127,15 +131,35 @@ static VALUE asn1pdu_to_der(VALUE self) { } ASN_STRUCT_FREE(*asn1pdu_type_descriptor(t), pdu_structure); - + return rb_str_new(derbuf, rv.encoded); } -void Init_asn1validator() { +static VALUE sha1collision_check(VALUE self, VALUE data) { + SHA1_CTX ctx; + unsigned char hash[20]; + SHA1DCInit(&ctx); + int iscoll; + + SHA1DCSetSafeHash(&ctx, 0); + SHA1DCSetUseUBC(&ctx, 0); + SHA1DCUpdate(&ctx, RSTRING_PTR(data), RSTRING_LEN(data)); + iscoll = SHA1DCFinal(hash,&ctx); + + if (iscoll) { + return Qtrue; + } else { + return Qfalse; + } +} + +void Init_certlint_ext() { mod_certlint = rb_define_module("CertLint"); class_certlint_asn1pdu = rb_define_class_under(mod_certlint, "ASN1Validator", rb_cObject); rb_define_method(class_certlint_asn1pdu, "initialize", asn1pdu_initialize, 2); rb_define_method(class_certlint_asn1pdu, "check_constraints", asn1pdu_check_constraints, 0); rb_define_method(class_certlint_asn1pdu, "to_der", asn1pdu_to_der, 0); + + rb_define_singleton_method(mod_certlint, "sha1_collision?", sha1collision_check, 1); } diff --git a/ext/extconf.rb b/ext/extconf.rb index 44b1491..170827b 100644 --- a/ext/extconf.rb +++ b/ext/extconf.rb @@ -1,7 +1,7 @@ require "mkmf" -dir_config("asn1validator") +dir_config("certlint_ext") $srcs = Dir["*.c"] -create_makefile("asn1validator") +create_makefile("certlint_ext") diff --git a/lib/certlint.rb b/lib/certlint.rb index 664ffcc..dcf3363 100644 --- a/lib/certlint.rb +++ b/lib/certlint.rb @@ -3,3 +3,4 @@ require 'certlint/pemlint' require 'certlint/namelint' require 'certlint/generalnames' +require 'certlint/sha1lint' diff --git a/lib/certlint/certlint.rb b/lib/certlint/certlint.rb index a046aab..c10c313 100755 --- a/lib/certlint/certlint.rb +++ b/lib/certlint/certlint.rb @@ -13,7 +13,7 @@ # express or implied. See the License for the specific language governing # permissions and limitations under the License. require 'rubygems' -require 'asn1validator' +require 'certlint_ext' require 'openssl' require_relative 'namelint' @@ -262,6 +262,9 @@ def self.lint(der) return messages end + # Check for SHAnanigans + messages += SHA1Lint.lint(der) + # Check time fields OpenSSL::ASN1.traverse(der) do |_depth, offset, header_len, length, _constructed, tag_class, tag| start_c = offset + header_len diff --git a/lib/certlint/sha1lint.rb b/lib/certlint/sha1lint.rb new file mode 100644 index 0000000..dc4f511 --- /dev/null +++ b/lib/certlint/sha1lint.rb @@ -0,0 +1,28 @@ +#!/usr/bin/ruby -Eutf-8:utf-8 +# encoding: UTF-8 +# Copyright 2017 Matt Palmer . All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You may not +# use this file except in compliance with the License. A copy of the License +# is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is distributed on +# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. + +module CertLint + class SHA1Lint + def self.lint(der) + messages = [] + + if CertLint.sha1_collision?(der.to_s) + messages << 'E: SHA1 collision attempt detected' + end + + messages + end + end +end