Repository: zmap/zlint
Pull request #286 (2019-05-31 23:10:18)

lints for RFC4055 check RSA AlgorithmIdentifier param is NULL for SPKI and tbsCertificate.signature
PR based on #285.

Benchmarked a byte compare of a precomputed algo byte slice, but performance difference was negligible and cryptobyte version more readable.  

tadukurow: I updated travis to run go1.12.5 as the gofmt test failed and passed on latest. Turned out it was some local go version shenanigans on my machine. Happy to revert back to go1.12 if necessary.
cpu: @tadukurow I think this is ready to be merged. I'm going to give it ~24 hrs in case @sleevi or @zakird have cycles to give it a quick :mag:. I will merge this Monday afternoon otherwise. I think its a definite improvement over the status quo and we can always iterate if there is late feedback. Thanks again for pitching in on this lint! :tada:
sleevi: Looks like the email gateway didn’t work.

I don’t think this is a good approach, and is exactly the approach I was trying to warn against with the design of the Mozilla PR, because it still allows invalid ASN.1 through.

In this case, for example, I could use a compound NULL or a non-zero-length NULL to include additional data. The first check for these really should be the memcmp - there is only a single possible correct encoding. The remainder - using or parsing ASN.1 - is merely about finding out where the issue is.
cpu: > I don’t think this is a good approach, and is exactly the approach I was trying to warn against with the design of the Mozilla PR, because it still allows invalid ASN.1 through.

I think its better than nothing (in that it will fail otherwise passing missisuances found in CT) but I definitely see your point. I'm sorry its taken me three passes to really catch on to the emphasis on a direct memcpy against the one correct encoding :man_facepalming: 

> In this case, for example, I could use a compound NULL or a non-zero-length NULL to include additional data. The first check for these really should be the memcmp - there is only a single possible correct encoding. The remainder - using or parsing ASN.1 - is merely about finding out where the issue is.

@tadukurow Can you lightly refactor this branch to determine the pass/fail status based on a byte-wise comparison against the known correct encoding and use the existing `cryptobyte` code to compute the detail field.

sleevi: If you need help making adversarial test cases - https://github.com/google/der-ascii is incredibly useful and what I use for this. You can easily mutate an SPKI into various forms with some simple commands, and then sign it as a cert with a test key.
tadukurow: @sleevi thanks for the link to ascii-der, very useful. 

Added some test cases by changing spki only to not blow up test certs and get around some pem parsing issues with malformed asn1.

I've expanded the cryptobyte check. It now makes sure the NULL tag is definitely empty and to check there is no trailing data. Also added the byte comparison check. 
tadukurow: I think all issues have been addressed, I've added a lint for TBS signature field as well.
cpu: @tadukurow Can you update the PR description so there is a good commit message ready for a squash merge? The branch was split into two separate lints and I think the description needs catching-up. It seems like CI is maybe stuck and perhaps another commit on the branch will :boot: it.

@sleevi I gave this a close review and I believe the way that it is implemented now meets the spirit of your feedback so far. I'd appreciate if you could give this one more :mag: if you had time. Particularly the added `lint_tbs_signature_rsa_encryption_parameter_not_null.go` lint for the signature algorithm parameters. Does the `cryptobyte` parsing of `c.RawTBSCertificate` to get to the DER encoded algorithm identifier look OK to you? 
sleevi: @cpu Yeah, I think this addresses my feedback as well. Thanks @tadukurow for humouring me in going for a thorough lint here. This definitely helps!
cpu: I'm going to give Travis and Github some time to un-stick whatever job queue is holding the CI status for this branch back. If it hasn't un-stuck in the next 24hrs I'll figure out how to unstick it so it can be merged.
tadukurow: My pleasure. Saw a few places where cryptobyte could give some more precision, so now the dependency is in I'll probably open one or two follow up PRs for that. 

zakird: I kicked travis. Should be good to go.

Patch:
diff --git a/.travis.yml b/.travis.yml
index 90f01138..0decee47 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,7 @@ language: go
 dist: trusty
 
 go:
-  - "1.12"
+  - "1.12.x"
 
 script:
   - make
diff --git a/go.mod b/go.mod
index 1d4b3c62..1f633589 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ require (
 	github.com/sirupsen/logrus v1.3.0
 	github.com/weppos/publicsuffix-go v0.4.0
 	github.com/zmap/zcrypto v0.0.0-20190329181646-dff83107394d
+	golang.org/x/crypto v0.0.0-20180904163835-0709b304e793
 	golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95
 	golang.org/x/text v0.3.0
 )
diff --git a/lints/lint_spki_rsa_encryption_parameter_not_null.go b/lints/lint_spki_rsa_encryption_parameter_not_null.go
new file mode 100644
index 00000000..15637122
--- /dev/null
+++ b/lints/lint_spki_rsa_encryption_parameter_not_null.go
@@ -0,0 +1,73 @@
+package lints
+
+/*
+ * ZLint Copyright 2019 Regents of the University of Michigan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.
+ */
+
+/*******************************************************************************************************
+"RFC5280: RFC 4055, Section 1.2"
+RSA: Encoded algorithm identifier MUST have NULL parameters.
+*******************************************************************************************************/
+
+import (
+	"fmt"
+
+	"github.com/zmap/zcrypto/x509"
+	"github.com/zmap/zlint/util"
+	"golang.org/x/crypto/cryptobyte"
+	cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
+)
+
+type rsaSPKIEncryptionParamNotNULL struct{}
+
+func (l *rsaSPKIEncryptionParamNotNULL) Initialize() error {
+	return nil
+}
+
+func (l *rsaSPKIEncryptionParamNotNULL) CheckApplies(c *x509.Certificate) bool {
+	// explicitly check for util.OidRSAEncryption, as RSA-PSS or RSA-OAEP certificates might be classified with c.PublicKeyAlgorithm = RSA
+	return c.PublicKeyAlgorithmOID.Equal(util.OidRSAEncryption)
+}
+
+func (l *rsaSPKIEncryptionParamNotNULL) Execute(c *x509.Certificate) *LintResult {
+	input := cryptobyte.String(c.RawSubjectPublicKeyInfo)
+
+	var publicKeyInfo cryptobyte.String
+	if !input.ReadASN1(&publicKeyInfo, cryptobyte_asn1.SEQUENCE) {
+		return &LintResult{Status: Fatal, Details: "error reading pkixPublicKey"}
+	}
+
+	var algorithm cryptobyte.String
+	var tag cryptobyte_asn1.Tag
+	// use ReadAnyElement to preserve tag and length octets
+	if !publicKeyInfo.ReadAnyASN1Element(&algorithm, &tag) {
+		return &LintResult{Status: Fatal, Details: "error reading pkixPublicKey"}
+	}
+
+	if err := util.CheckAlgorithmIDParamNotNULL(algorithm, util.OidRSAEncryption); err != nil {
+		return &LintResult{Status: Error, Details: fmt.Sprintf("certificate pkixPublicKey %s", err.Error())}
+	}
+
+	return &LintResult{Status: Pass}
+}
+
+func init() {
+	RegisterLint(&Lint{
+		Name:          "e_spki_rsa_encryption_parameter_not_null",
+		Description:   "RSA: Encoded public key algorithm identifier MUST have NULL parameters",
+		Citation:      "RFC 4055, Section 1.2",
+		Source:        RFC5280, // RFC4055 is referenced in RFC5280, Section 1
+		EffectiveDate: util.RFC5280Date,
+		Lint:          &rsaSPKIEncryptionParamNotNULL{},
+	})
+}
diff --git a/lints/lint_tbs_signature_rsa_encryption_parameter_not_null.go b/lints/lint_tbs_signature_rsa_encryption_parameter_not_null.go
new file mode 100644
index 00000000..f29dae38
--- /dev/null
+++ b/lints/lint_tbs_signature_rsa_encryption_parameter_not_null.go
@@ -0,0 +1,81 @@
+package lints
+
+/*
+ * ZLint Copyright 2019 Regents of the University of Michigan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.
+ */
+
+/*******************************************************************************************************
+"RFC5280: RFC 4055, Section 5"
+RSA: Encoded algorithm identifier MUST have NULL parameters.
+*******************************************************************************************************/
+
+import (
+	"fmt"
+
+	"github.com/zmap/zcrypto/x509"
+	"github.com/zmap/zlint/util"
+	"golang.org/x/crypto/cryptobyte"
+	cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
+)
+
+type rsaTBSSignatureEncryptionParamNotNULL struct{}
+
+func (l *rsaTBSSignatureEncryptionParamNotNULL) Initialize() error {
+	return nil
+}
+
+func (l *rsaTBSSignatureEncryptionParamNotNULL) CheckApplies(c *x509.Certificate) bool {
+	_, ok := util.RSAAlgorithmIDToDER[c.SignatureAlgorithmOID.String()]
+	return ok
+}
+
+func (l *rsaTBSSignatureEncryptionParamNotNULL) Execute(c *x509.Certificate) *LintResult {
+	input := cryptobyte.String(c.RawTBSCertificate)
+
+	var tbsCert cryptobyte.String
+	if !input.ReadASN1(&tbsCert, cryptobyte_asn1.SEQUENCE) {
+		return &LintResult{Status: Fatal, Details: "error reading tbsCertificate"}
+	}
+
+	if !tbsCert.SkipOptionalASN1(cryptobyte_asn1.Tag(0).Constructed().ContextSpecific()) {
+		return &LintResult{Status: Fatal, Details: "error reading tbsCertificate.version"}
+	}
+
+	if !tbsCert.SkipASN1(cryptobyte_asn1.INTEGER) {
+		return &LintResult{Status: Fatal, Details: "error reading tbsCertificate.serialNumber"}
+	}
+
+	var signatureAlgoID cryptobyte.String
+	var tag cryptobyte_asn1.Tag
+	// use ReadAnyElement to preserve tag and length octets
+	if !tbsCert.ReadAnyASN1Element(&signatureAlgoID, &tag) {
+		return &LintResult{Status: Fatal, Details: "error reading tbsCertificate.signature"}
+	}
+
+	if err := util.CheckAlgorithmIDParamNotNULL(signatureAlgoID, c.SignatureAlgorithmOID); err != nil {
+		return &LintResult{Status: Error, Details: fmt.Sprintf("certificate tbsCertificate.signature %s", err.Error())}
+	}
+
+	return &LintResult{Status: Pass}
+}
+
+func init() {
+	RegisterLint(&Lint{
+		Name:          "e_tbs_signature_rsa_encryption_parameter_not_null",
+		Description:   "RSA: Encoded signature algorithm identifier MUST have NULL parameters",
+		Citation:      "RFC 4055, Section 5",
+		Source:        RFC5280, // RFC4055 is referenced in RFC5280, Section 1
+		EffectiveDate: util.RFC5280Date,
+		Lint:          &rsaTBSSignatureEncryptionParamNotNULL{},
+	})
+}
diff --git a/util/algorithm_identifier.go b/util/algorithm_identifier.go
new file mode 100644
index 00000000..921b2867
--- /dev/null
+++ b/util/algorithm_identifier.go
@@ -0,0 +1,86 @@
+package util
+
+import (
+	"bytes"
+	"encoding/asn1"
+	"errors"
+	"fmt"
+
+	"golang.org/x/crypto/cryptobyte"
+	cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
+)
+
+// RSAAlgorithmIDToDER contains DER representations of pkix.AlgorithmIdentifier for different RSA OIDs with Parameters as asn1.NULL
+var RSAAlgorithmIDToDER = map[string][]byte{
+	// rsaEncryption
+	"1.2.840.113549.1.1.1": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1, 0x5, 0x0},
+	// md2WithRSAEncryption
+	"1.2.840.113549.1.1.2": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x2, 0x5, 0x0},
+	// md5WithRSAEncryption
+	"1.2.840.113549.1.1.4": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x4, 0x5, 0x0},
+	// sha-1WithRSAEncryption
+	"1.2.840.113549.1.1.5": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x5, 0x5, 0x0},
+	// sha224WithRSAEncryption
+	"1.2.840.113549.1.1.14": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xe, 0x5, 0x0},
+	// sha256WithRSAEncryption
+	"1.2.840.113549.1.1.11": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xb, 0x5, 0x0},
+	// sha384WithRSAEncryption
+	"1.2.840.113549.1.1.12": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xc, 0x5, 0x0},
+	// sha512WithRSAEncryption
+	"1.2.840.113549.1.1.13": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xd, 0x5, 0x0},
+}
+
+// CheckAlgorithmIDParamNotNULL parses an AlgorithmIdentifier with algorithm OID rsaEncryption to check the Param field is asn1.NULL
+// Expects DER-encoded AlgorithmIdentifier including tag and length
+func CheckAlgorithmIDParamNotNULL(algorithmIdentifier []byte, requiredAlgoID asn1.ObjectIdentifier) error {
+	expectedAlgoIDBytes, ok := RSAAlgorithmIDToDER[requiredAlgoID.String()]
+	if !ok {
+		return errors.New("error algorithmID to check is not RSA")
+	}
+
+	algorithmSequence := cryptobyte.String(algorithmIdentifier)
+
+	// byte comparison of algorithm sequence and checking no trailing data is present
+	var algorithmBytes []byte
+	if algorithmSequence.ReadBytes(&algorithmBytes, len(expectedAlgoIDBytes)) {
+		if bytes.Compare(algorithmBytes, expectedAlgoIDBytes) == 0 && algorithmSequence.Empty() {
+			return nil
+		}
+	}
+
+	// re-parse to get an error message detailing what did not match in the byte comparison
+	algorithmSequence = cryptobyte.String(algorithmIdentifier)
+	var algorithm cryptobyte.String
+	if !algorithmSequence.ReadASN1(&algorithm, cryptobyte_asn1.SEQUENCE) {
+		return errors.New("error reading algorithm")
+	}
+
+	encryptionOID := asn1.ObjectIdentifier{}
+	if !algorithm.ReadASN1ObjectIdentifier(&encryptionOID) {
+		return errors.New("error reading algorithm OID")
+	}
+
+	if !encryptionOID.Equal(requiredAlgoID) {
+		return fmt.Errorf("algorithm OID is not equal to %s", requiredAlgoID.String())
+	}
+
+	if algorithm.Empty() {
+		return errors.New("RSA algorithm identifier missing required NULL parameter")
+	}
+
+	var nullValue cryptobyte.String
+	if !algorithm.ReadASN1(&nullValue, cryptobyte_asn1.NULL) {
+		return errors.New("RSA algorithm identifier with non-NULL parameter")
+	}
+
+	if len(nullValue) != 0 {
+		return errors.New("RSA algorithm identifier with NULL parameter containing data")
+	}
+
+	// ensure algorithm is empty and no trailing data is present
+	if !algorithm.Empty() {
+		return errors.New("RSA algorithm identifier with trailing data")
+	}
+
+	return errors.New("RSA algorithm appears correct, but didn't match byte-wise comparison")
+}
diff --git a/util/oid.go b/util/oid.go
index 082e1b3b..8a976117 100644
--- a/util/oid.go
+++ b/util/oid.go
@@ -71,7 +71,15 @@ var (
 	SHA384OID = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2}
 	SHA512OID = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3}
 	// other OIDs
+	OidRSAEncryption           = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
 	OidRSASSAPSS               = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10}
+	OidMD2WithRSAEncryption    = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2}
+	OidMD5WithRSAEncryption    = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4}
+	OidSHA1WithRSAEncryption   = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}
+	OidSHA224WithRSAEncryption = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 14}
+	OidSHA256WithRSAEncryption = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}
+	OidSHA384WithRSAEncryption = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12}
+	OidSHA512WithRSAEncryption = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13}
 	AnyPolicyOID               = asn1.ObjectIdentifier{2, 5, 29, 32, 0}
 	UserNoticeOID              = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 2, 2}
 	CpsOID                     = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 2, 1}
diff --git a/vendor/golang.org/x/crypto/cryptobyte/asn1.go b/vendor/golang.org/x/crypto/cryptobyte/asn1.go
new file mode 100644
index 00000000..528b9bff
--- /dev/null
+++ b/vendor/golang.org/x/crypto/cryptobyte/asn1.go
@@ -0,0 +1,751 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cryptobyte
+
+import (
+	encoding_asn1 "encoding/asn1"
+	"fmt"
+	"math/big"
+	"reflect"
+	"time"
+
+	"golang.org/x/crypto/cryptobyte/asn1"
+)
+
+// This file contains ASN.1-related methods for String and Builder.
+
+// Builder
+
+// AddASN1Int64 appends a DER-encoded ASN.1 INTEGER.
+func (b *Builder) AddASN1Int64(v int64) {
+	b.addASN1Signed(asn1.INTEGER, v)
+}
+
+// AddASN1Int64WithTag appends a DER-encoded ASN.1 INTEGER with the
+// given tag.
+func (b *Builder) AddASN1Int64WithTag(v int64, tag asn1.Tag) {
+	b.addASN1Signed(tag, v)
+}
+
+// AddASN1Enum appends a DER-encoded ASN.1 ENUMERATION.
+func (b *Builder) AddASN1Enum(v int64) {
+	b.addASN1Signed(asn1.ENUM, v)
+}
+
+func (b *Builder) addASN1Signed(tag asn1.Tag, v int64) {
+	b.AddASN1(tag, func(c *Builder) {
+		length := 1
+		for i := v; i >= 0x80 || i < -0x80; i >>= 8 {
+			length++
+		}
+
+		for ; length > 0; length-- {
+			i := v >> uint((length-1)*8) & 0xff
+			c.AddUint8(uint8(i))
+		}
+	})
+}
+
+// AddASN1Uint64 appends a DER-encoded ASN.1 INTEGER.
+func (b *Builder) AddASN1Uint64(v uint64) {
+	b.AddASN1(asn1.INTEGER, func(c *Builder) {
+		length := 1
+		for i := v; i >= 0x80; i >>= 8 {
+			length++
+		}
+
+		for ; length > 0; length-- {
+			i := v >> uint((length-1)*8) & 0xff
+			c.AddUint8(uint8(i))
+		}
+	})
+}
+
+// AddASN1BigInt appends a DER-encoded ASN.1 INTEGER.
+func (b *Builder) AddASN1BigInt(n *big.Int) {
+	if b.err != nil {
+		return
+	}
+
+	b.AddASN1(asn1.INTEGER, func(c *Builder) {
+		if n.Sign() < 0 {
+			// A negative number has to be converted to two's-complement form. So we
+			// invert and subtract 1. If the most-significant-bit isn't set then
+			// we'll need to pad the beginning with 0xff in order to keep the number
+			// negative.
+			nMinus1 := new(big.Int).Neg(n)
+			nMinus1.Sub(nMinus1, bigOne)
+			bytes := nMinus1.Bytes()
+			for i := range bytes {
+				bytes[i] ^= 0xff
+			}
+			if bytes[0]&0x80 == 0 {
+				c.add(0xff)
+			}
+			c.add(bytes...)
+		} else if n.Sign() == 0 {
+			c.add(0)
+		} else {
+			bytes := n.Bytes()
+			if bytes[0]&0x80 != 0 {
+				c.add(0)
+			}
+			c.add(bytes...)
+		}
+	})
+}
+
+// AddASN1OctetString appends a DER-encoded ASN.1 OCTET STRING.
+func (b *Builder) AddASN1OctetString(bytes []byte) {
+	b.AddASN1(asn1.OCTET_STRING, func(c *Builder) {
+		c.AddBytes(bytes)
+	})
+}
+
+const generalizedTimeFormatStr = "20060102150405Z0700"
+
+// AddASN1GeneralizedTime appends a DER-encoded ASN.1 GENERALIZEDTIME.
+func (b *Builder) AddASN1GeneralizedTime(t time.Time) {
+	if t.Year() < 0 || t.Year() > 9999 {
+		b.err = fmt.Errorf("cryptobyte: cannot represent %v as a GeneralizedTime", t)
+		return
+	}
+	b.AddASN1(asn1.GeneralizedTime, func(c *Builder) {
+		c.AddBytes([]byte(t.Format(generalizedTimeFormatStr)))
+	})
+}
+
+// AddASN1BitString appends a DER-encoded ASN.1 BIT STRING. This does not
+// support BIT STRINGs that are not a whole number of bytes.
+func (b *Builder) AddASN1BitString(data []byte) {
+	b.AddASN1(asn1.BIT_STRING, func(b *Builder) {
+		b.AddUint8(0)
+		b.AddBytes(data)
+	})
+}
+
+func (b *Builder) addBase128Int(n int64) {
+	var length int
+	if n == 0 {
+		length = 1
+	} else {
+		for i := n; i > 0; i >>= 7 {
+			length++
+		}
+	}
+
+	for i := length - 1; i >= 0; i-- {
+		o := byte(n >> uint(i*7))
+		o &= 0x7f
+		if i != 0 {
+			o |= 0x80
+		}
+
+		b.add(o)
+	}
+}
+
+func isValidOID(oid encoding_asn1.ObjectIdentifier) bool {
+	if len(oid) < 2 {
+		return false
+	}
+
+	if oid[0] > 2 || (oid[0] <= 1 && oid[1] >= 40) {
+		return false
+	}
+
+	for _, v := range oid {
+		if v < 0 {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (b *Builder) AddASN1ObjectIdentifier(oid encoding_asn1.ObjectIdentifier) {
+	b.AddASN1(asn1.OBJECT_IDENTIFIER, func(b *Builder) {
+		if !isValidOID(oid) {
+			b.err = fmt.Errorf("cryptobyte: invalid OID: %v", oid)
+			return
+		}
+
+		b.addBase128Int(int64(oid[0])*40 + int64(oid[1]))
+		for _, v := range oid[2:] {
+			b.addBase128Int(int64(v))
+		}
+	})
+}
+
+func (b *Builder) AddASN1Boolean(v bool) {
+	b.AddASN1(asn1.BOOLEAN, func(b *Builder) {
+		if v {
+			b.AddUint8(0xff)
+		} else {
+			b.AddUint8(0)
+		}
+	})
+}
+
+func (b *Builder) AddASN1NULL() {
+	b.add(uint8(asn1.NULL), 0)
+}
+
+// MarshalASN1 calls encoding_asn1.Marshal on its input and appends the result if
+// successful or records an error if one occurred.
+func (b *Builder) MarshalASN1(v interface{}) {
+	// NOTE(martinkr): This is somewhat of a hack to allow propagation of
+	// encoding_asn1.Marshal errors into Builder.err. N.B. if you call MarshalASN1 with a
+	// value embedded into a struct, its tag information is lost.
+	if b.err != nil {
+		return
+	}
+	bytes, err := encoding_asn1.Marshal(v)
+	if err != nil {
+		b.err = err
+		return
+	}
+	b.AddBytes(bytes)
+}
+
+// AddASN1 appends an ASN.1 object. The object is prefixed with the given tag.
+// Tags greater than 30 are not supported and result in an error (i.e.
+// low-tag-number form only). The child builder passed to the
+// BuilderContinuation can be used to build the content of the ASN.1 object.
+func (b *Builder) AddASN1(tag asn1.Tag, f BuilderContinuation) {
+	if b.err != nil {
+		return
+	}
+	// Identifiers with the low five bits set indicate high-tag-number format
+	// (two or more octets), which we don't support.
+	if tag&0x1f == 0x1f {
+		b.err = fmt.Errorf("cryptobyte: high-tag number identifier octects not supported: 0x%x", tag)
+		return
+	}
+	b.AddUint8(uint8(tag))
+	b.addLengthPrefixed(1, true, f)
+}
+
+// String
+
+// ReadASN1Boolean decodes an ASN.1 INTEGER and converts it to a boolean
+// representation into out and advances. It reports whether the read
+// was successful.
+func (s *String) ReadASN1Boolean(out *bool) bool {
+	var bytes String
+	if !s.ReadASN1(&bytes, asn1.INTEGER) || len(bytes) != 1 {
+		return false
+	}
+
+	switch bytes[0] {
+	case 0:
+		*out = false
+	case 0xff:
+		*out = true
+	default:
+		return false
+	}
+
+	return true
+}
+
+var bigIntType = reflect.TypeOf((*big.Int)(nil)).Elem()
+
+// ReadASN1Integer decodes an ASN.1 INTEGER into out and advances. If out does
+// not point to an integer or to a big.Int, it panics. It reports whether the
+// read was successful.
+func (s *String) ReadASN1Integer(out interface{}) bool {
+	if reflect.TypeOf(out).Kind() != reflect.Ptr {
+		panic("out is not a pointer")
+	}
+	switch reflect.ValueOf(out).Elem().Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		var i int64
+		if !s.readASN1Int64(&i) || reflect.ValueOf(out).Elem().OverflowInt(i) {
+			return false
+		}
+		reflect.ValueOf(out).Elem().SetInt(i)
+		return true
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		var u uint64
+		if !s.readASN1Uint64(&u) || reflect.ValueOf(out).Elem().OverflowUint(u) {
+			return false
+		}
+		reflect.ValueOf(out).Elem().SetUint(u)
+		return true
+	case reflect.Struct:
+		if reflect.TypeOf(out).Elem() == bigIntType {
+			return s.readASN1BigInt(out.(*big.Int))
+		}
+	}
+	panic("out does not point to an integer type")
+}
+
+func checkASN1Integer(bytes []byte) bool {
+	if len(bytes) == 0 {
+		// An INTEGER is encoded with at least one octet.
+		return false
+	}
+	if len(bytes) == 1 {
+		return true
+	}
+	if bytes[0] == 0 && bytes[1]&0x80 == 0 || bytes[0] == 0xff && bytes[1]&0x80 == 0x80 {
+		// Value is not minimally encoded.
+		return false
+	}
+	return true
+}
+
+var bigOne = big.NewInt(1)
+
+func (s *String) readASN1BigInt(out *big.Int) bool {
+	var bytes String
+	if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) {
+		return false
+	}
+	if bytes[0]&0x80 == 0x80 {
+		// Negative number.
+		neg := make([]byte, len(bytes))
+		for i, b := range bytes {
+			neg[i] = ^b
+		}
+		out.SetBytes(neg)
+		out.Add(out, bigOne)
+		out.Neg(out)
+	} else {
+		out.SetBytes(bytes)
+	}
+	return true
+}
+
+func (s *String) readASN1Int64(out *int64) bool {
+	var bytes String
+	if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Signed(out, bytes) {
+		return false
+	}
+	return true
+}
+
+func asn1Signed(out *int64, n []byte) bool {
+	length := len(n)
+	if length > 8 {
+		return false
+	}
+	for i := 0; i < length; i++ {
+		*out <<= 8
+		*out |= int64(n[i])
+	}
+	// Shift up and down in order to sign extend the result.
+	*out <<= 64 - uint8(length)*8
+	*out >>= 64 - uint8(length)*8
+	return true
+}
+
+func (s *String) readASN1Uint64(out *uint64) bool {
+	var bytes String
+	if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Unsigned(out, bytes) {
+		return false
+	}
+	return true
+}
+
+func asn1Unsigned(out *uint64, n []byte) bool {
+	length := len(n)
+	if length > 9 || length == 9 && n[0] != 0 {
+		// Too large for uint64.
+		return false
+	}
+	if n[0]&0x80 != 0 {
+		// Negative number.
+		return false
+	}
+	for i := 0; i < length; i++ {
+		*out <<= 8
+		*out |= uint64(n[i])
+	}
+	return true
+}
+
+// ReadASN1Int64WithTag decodes an ASN.1 INTEGER with the given tag into out
+// and advances. It reports whether the read was successful and resulted in a
+// value that can be represented in an int64.
+func (s *String) ReadASN1Int64WithTag(out *int64, tag asn1.Tag) bool {
+	var bytes String
+	return s.ReadASN1(&bytes, tag) && checkASN1Integer(bytes) && asn1Signed(out, bytes)
+}
+
+// ReadASN1Enum decodes an ASN.1 ENUMERATION into out and advances. It reports
+// whether the read was successful.
+func (s *String) ReadASN1Enum(out *int) bool {
+	var bytes String
+	var i int64
+	if !s.ReadASN1(&bytes, asn1.ENUM) || !checkASN1Integer(bytes) || !asn1Signed(&i, bytes) {
+		return false
+	}
+	if int64(int(i)) != i {
+		return false
+	}
+	*out = int(i)
+	return true
+}
+
+func (s *String) readBase128Int(out *int) bool {
+	ret := 0
+	for i := 0; len(*s) > 0; i++ {
+		if i == 4 {
+			return false
+		}
+		ret <<= 7
+		b := s.read(1)[0]
+		ret |= int(b & 0x7f)
+		if b&0x80 == 0 {
+			*out = ret
+			return true
+		}
+	}
+	return false // truncated
+}
+
+// ReadASN1ObjectIdentifier decodes an ASN.1 OBJECT IDENTIFIER into out and
+// advances. It reports whether the read was successful.
+func (s *String) ReadASN1ObjectIdentifier(out *encoding_asn1.ObjectIdentifier) bool {
+	var bytes String
+	if !s.ReadASN1(&bytes, asn1.OBJECT_IDENTIFIER) || len(bytes) == 0 {
+		return false
+	}
+
+	// In the worst case, we get two elements from the first byte (which is
+	// encoded differently) and then every varint is a single byte long.
+	components := make([]int, len(bytes)+1)
+
+	// The first varint is 40*value1 + value2:
+	// According to this packing, value1 can take the values 0, 1 and 2 only.
+	// When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2,
+	// then there are no restrictions on value2.
+	var v int
+	if !bytes.readBase128Int(&v) {
+		return false
+	}
+	if v < 80 {
+		components[0] = v / 40
+		components[1] = v % 40
+	} else {
+		components[0] = 2
+		components[1] = v - 80
+	}
+
+	i := 2
+	for ; len(bytes) > 0; i++ {
+		if !bytes.readBase128Int(&v) {
+			return false
+		}
+		components[i] = v
+	}
+	*out = components[:i]
+	return true
+}
+
+// ReadASN1GeneralizedTime decodes an ASN.1 GENERALIZEDTIME into out and
+// advances. It reports whether the read was successful.
+func (s *String) ReadASN1GeneralizedTime(out *time.Time) bool {
+	var bytes String
+	if !s.ReadASN1(&bytes, asn1.GeneralizedTime) {
+		return false
+	}
+	t := string(bytes)
+	res, err := time.Parse(generalizedTimeFormatStr, t)
+	if err != nil {
+		return false
+	}
+	if serialized := res.Format(generalizedTimeFormatStr); serialized != t {
+		return false
+	}
+	*out = res
+	return true
+}
+
+// ReadASN1BitString decodes an ASN.1 BIT STRING into out and advances.
+// It reports whether the read was successful.
+func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool {
+	var bytes String
+	if !s.ReadASN1(&bytes, asn1.BIT_STRING) || len(bytes) == 0 {
+		return false
+	}
+
+	paddingBits := uint8(bytes[0])
+	bytes = bytes[1:]
+	if paddingBits > 7 ||
+		len(bytes) == 0 && paddingBits != 0 ||
+		len(bytes) > 0 && bytes[len(bytes)-1]&(1<<paddingBits-1) != 0 {
+		return false
+	}
+
+	out.BitLength = len(bytes)*8 - int(paddingBits)
+	out.Bytes = bytes
+	return true
+}
+
+// ReadASN1BitString decodes an ASN.1 BIT STRING into out and advances. It is
+// an error if the BIT STRING is not a whole number of bytes. It reports
+// whether the read was successful.
+func (s *String) ReadASN1BitStringAsBytes(out *[]byte) bool {
+	var bytes String
+	if !s.ReadASN1(&bytes, asn1.BIT_STRING) || len(bytes) == 0 {
+		return false
+	}
+
+	paddingBits := uint8(bytes[0])
+	if paddingBits != 0 {
+		return false
+	}
+	*out = bytes[1:]
+	return true
+}
+
+// ReadASN1Bytes reads the contents of a DER-encoded ASN.1 element (not including
+// tag and length bytes) into out, and advances. The element must match the
+// given tag. It reports whether the read was successful.
+func (s *String) ReadASN1Bytes(out *[]byte, tag asn1.Tag) bool {
+	return s.ReadASN1((*String)(out), tag)
+}
+
+// ReadASN1 reads the contents of a DER-encoded ASN.1 element (not including
+// tag and length bytes) into out, and advances. The element must match the
+// given tag. It reports whether the read was successful.
+//
+// Tags greater than 30 are not supported (i.e. low-tag-number format only).
+func (s *String) ReadASN1(out *String, tag asn1.Tag) bool {
+	var t asn1.Tag
+	if !s.ReadAnyASN1(out, &t) || t != tag {
+		return false
+	}
+	return true
+}
+
+// ReadASN1Element reads the contents of a DER-encoded ASN.1 element (including
+// tag and length bytes) into out, and advances. The element must match the
+// given tag. It reports whether the read was successful.
+//
+// Tags greater than 30 are not supported (i.e. low-tag-number format only).
+func (s *String) ReadASN1Element(out *String, tag asn1.Tag) bool {
+	var t asn1.Tag
+	if !s.ReadAnyASN1Element(out, &t) || t != tag {
+		return false
+	}
+	return true
+}
+
+// ReadAnyASN1 reads the contents of a DER-encoded ASN.1 element (not including
+// tag and length bytes) into out, sets outTag to its tag, and advances.
+// It reports whether the read was successful.
+//
+// Tags greater than 30 are not supported (i.e. low-tag-number format only).
+func (s *String) ReadAnyASN1(out *String, outTag *asn1.Tag) bool {
+	return s.readASN1(out, outTag, true /* skip header */)
+}
+
+// ReadAnyASN1Element reads the contents of a DER-encoded ASN.1 element
+// (including tag and length bytes) into out, sets outTag to is tag, and
+// advances. It reports whether the read was successful.
+//
+// Tags greater than 30 are not supported (i.e. low-tag-number format only).
+func (s *String) ReadAnyASN1Element(out *String, outTag *asn1.Tag) bool {
+	return s.readASN1(out, outTag, false /* include header */)
+}
+
+// PeekASN1Tag reports whether the next ASN.1 value on the string starts with
+// the given tag.
+func (s String) PeekASN1Tag(tag asn1.Tag) bool {
+	if len(s) == 0 {
+		return false
+	}
+	return asn1.Tag(s[0]) == tag
+}
+
+// SkipASN1 reads and discards an ASN.1 element with the given tag. It
+// reports whether the operation was successful.
+func (s *String) SkipASN1(tag asn1.Tag) bool {
+	var unused String
+	return s.ReadASN1(&unused, tag)
+}
+
+// ReadOptionalASN1 attempts to read the contents of a DER-encoded ASN.1
+// element (not including tag and length bytes) tagged with the given tag into
+// out. It stores whether an element with the tag was found in outPresent,
+// unless outPresent is nil. It reports whether the read was successful.
+func (s *String) ReadOptionalASN1(out *String, outPresent *bool, tag asn1.Tag) bool {
+	present := s.PeekASN1Tag(tag)
+	if outPresent != nil {
+		*outPresent = present
+	}
+	if present && !s.ReadASN1(out, tag) {
+		return false
+	}
+	return true
+}
+
+// SkipOptionalASN1 advances s over an ASN.1 element with the given tag, or
+// else leaves s unchanged. It reports whether the operation was successful.
+func (s *String) SkipOptionalASN1(tag asn1.Tag) bool {
+	if !s.PeekASN1Tag(tag) {
+		return true
+	}
+	var unused String
+	return s.ReadASN1(&unused, tag)
+}
+
+// ReadOptionalASN1Integer attempts to read an optional ASN.1 INTEGER
+// explicitly tagged with tag into out and advances. If no element with a
+// matching tag is present, it writes defaultValue into out instead. If out
+// does not point to an integer or to a big.Int, it panics. It reports
+// whether the read was successful.
+func (s *String) ReadOptionalASN1Integer(out interface{}, tag asn1.Tag, defaultValue interface{}) bool {
+	if reflect.TypeOf(out).Kind() != reflect.Ptr {
+		panic("out is not a pointer")
+	}
+	var present bool
+	var i String
+	if !s.ReadOptionalASN1(&i, &present, tag) {
+		return false
+	}
+	if !present {
+		switch reflect.ValueOf(out).Elem().Kind() {
+		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
+			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+			reflect.ValueOf(out).Elem().Set(reflect.ValueOf(defaultValue))
+		case reflect.Struct:
+			if reflect.TypeOf(out).Elem() != bigIntType {
+				panic("invalid integer type")
+			}
+			if reflect.TypeOf(defaultValue).Kind() != reflect.Ptr ||
+				reflect.TypeOf(defaultValue).Elem() != bigIntType {
+				panic("out points to big.Int, but defaultValue does not")
+			}
+			out.(*big.Int).Set(defaultValue.(*big.Int))
+		default:
+			panic("invalid integer type")
+		}
+		return true
+	}
+	if !i.ReadASN1Integer(out) || !i.Empty() {
+		return false
+	}
+	return true
+}
+
+// ReadOptionalASN1OctetString attempts to read an optional ASN.1 OCTET STRING
+// explicitly tagged with tag into out and advances. If no element with a
+// matching tag is present, it sets "out" to nil instead. It reports
+// whether the read was successful.
+func (s *String) ReadOptionalASN1OctetString(out *[]byte, outPresent *bool, tag asn1.Tag) bool {
+	var present bool
+	var child String
+	if !s.ReadOptionalASN1(&child, &present, tag) {
+		return false
+	}
+	if outPresent != nil {
+		*outPresent = present
+	}
+	if present {
+		var oct String
+		if !child.ReadASN1(&oct, asn1.OCTET_STRING) || !child.Empty() {
+			return false
+		}
+		*out = oct
+	} else {
+		*out = nil
+	}
+	return true
+}
+
+// ReadOptionalASN1Boolean sets *out to the value of the next ASN.1 BOOLEAN or,
+// if the next bytes are not an ASN.1 BOOLEAN, to the value of defaultValue.
+// It reports whether the operation was successful.
+func (s *String) ReadOptionalASN1Boolean(out *bool, defaultValue bool) bool {
+	var present bool
+	var child String
+	if !s.ReadOptionalASN1(&child, &present, asn1.BOOLEAN) {
+		return false
+	}
+
+	if !present {
+		*out = defaultValue
+		return true
+	}
+
+	return s.ReadASN1Boolean(out)
+}
+
+func (s *String) readASN1(out *String, outTag *asn1.Tag, skipHeader bool) bool {
+	if len(*s) < 2 {
+		return false
+	}
+	tag, lenByte := (*s)[0], (*s)[1]
+
+	if tag&0x1f == 0x1f {
+		// ITU-T X.690 section 8.1.2
+		//
+		// An identifier octet with a tag part of 0x1f indicates a high-tag-number
+		// form identifier with two or more octets. We only support tags less than
+		// 31 (i.e. low-tag-number form, single octet identifier).
+		return false
+	}
+
+	if outTag != nil {
+		*outTag = asn1.Tag(tag)
+	}
+
+	// ITU-T X.690 section 8.1.3
+	//
+	// Bit 8 of the first length byte indicates whether the length is short- or
+	// long-form.
+	var length, headerLen uint32 // length includes headerLen
+	if lenByte&0x80 == 0 {
+		// Short-form length (section 8.1.3.4), encoded in bits 1-7.
+		length = uint32(lenByte) + 2
+		headerLen = 2
+	} else {
+		// Long-form length (section 8.1.3.5). Bits 1-7 encode the number of octets
+		// used to encode the length.
+		lenLen := lenByte & 0x7f
+		var len32 uint32
+
+		if lenLen == 0 || lenLen > 4 || len(*s) < int(2+lenLen) {
+			return false
+		}
+
+		lenBytes := String((*s)[2 : 2+lenLen])
+		if !lenBytes.readUnsigned(&len32, int(lenLen)) {
+			return false
+		}
+
+		// ITU-T X.690 section 10.1 (DER length forms) requires encoding the length
+		// with the minimum number of octets.
+		if len32 < 128 {
+			// Length should have used short-form encoding.
+			return false
+		}
+		if len32>>((lenLen-1)*8) == 0 {
+			// Leading octet is 0. Length should have been at least one byte shorter.
+			return false
+		}
+
+		headerLen = 2 + uint32(lenLen)
+		if headerLen+len32 < len32 {
+			// Overflow.
+			return false
+		}
+		length = headerLen + len32
+	}
+
+	if uint32(int(length)) != length || !s.ReadBytes((*[]byte)(out), int(length)) {
+		return false
+	}
+	if skipHeader && !out.Skip(int(headerLen)) {
+		panic("cryptobyte: internal error")
+	}
+
+	return true
+}
diff --git a/vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go b/vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go
new file mode 100644
index 00000000..cda8e3ed
--- /dev/null
+++ b/vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go
@@ -0,0 +1,46 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package asn1 contains supporting types for parsing and building ASN.1
+// messages with the cryptobyte package.
+package asn1 // import "golang.org/x/crypto/cryptobyte/asn1"
+
+// Tag represents an ASN.1 identifier octet, consisting of a tag number
+// (indicating a type) and class (such as context-specific or constructed).
+//
+// Methods in the cryptobyte package only support the low-tag-number form, i.e.
+// a single identifier octet with bits 7-8 encoding the class and bits 1-6
+// encoding the tag number.
+type Tag uint8
+
+const (
+	classConstructed     = 0x20
+	classContextSpecific = 0x80
+)
+
+// Constructed returns t with the constructed class bit set.
+func (t Tag) Constructed() Tag { return t | classConstructed }
+
+// ContextSpecific returns t with the context-specific class bit set.
+func (t Tag) ContextSpecific() Tag { return t | classContextSpecific }
+
+// The following is a list of standard tag and class combinations.
+const (
+	BOOLEAN           = Tag(1)
+	INTEGER           = Tag(2)
+	BIT_STRING        = Tag(3)
+	OCTET_STRING      = Tag(4)
+	NULL              = Tag(5)
+	OBJECT_IDENTIFIER = Tag(6)
+	ENUM              = Tag(10)
+	UTF8String        = Tag(12)
+	SEQUENCE          = Tag(16 | classConstructed)
+	SET               = Tag(17 | classConstructed)
+	PrintableString   = Tag(19)
+	T61String         = Tag(20)
+	IA5String         = Tag(22)
+	UTCTime           = Tag(23)
+	GeneralizedTime   = Tag(24)
+	GeneralString     = Tag(27)
+)
diff --git a/vendor/golang.org/x/crypto/cryptobyte/builder.go b/vendor/golang.org/x/crypto/cryptobyte/builder.go
new file mode 100644
index 00000000..29b4c764
--- /dev/null
+++ b/vendor/golang.org/x/crypto/cryptobyte/builder.go
@@ -0,0 +1,309 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cryptobyte
+
+import (
+	"errors"
+	"fmt"
+)
+
+// A Builder builds byte strings from fixed-length and length-prefixed values.
+// Builders either allocate space as needed, or are ‘fixed’, which means that
+// they write into a given buffer and produce an error if it's exhausted.
+//
+// The zero value is a usable Builder that allocates space as needed.
+//
+// Simple values are marshaled and appended to a Builder using methods on the
+// Builder. Length-prefixed values are marshaled by providing a
+// BuilderContinuation, which is a function that writes the inner contents of
+// the value to a given Builder. See the documentation for BuilderContinuation
+// for details.
+type Builder struct {
+	err            error
+	result         []byte
+	fixedSize      bool
+	child          *Builder
+	offset         int
+	pendingLenLen  int
+	pendingIsASN1  bool
+	inContinuation *bool
+}
+
+// NewBuilder creates a Builder that appends its output to the given buffer.
+// Like append(), the slice will be reallocated if its capacity is exceeded.
+// Use Bytes to get the final buffer.
+func NewBuilder(buffer []byte) *Builder {
+	return &Builder{
+		result: buffer,
+	}
+}
+
+// NewFixedBuilder creates a Builder that appends its output into the given
+// buffer. This builder does not reallocate the output buffer. Writes that
+// would exceed the buffer's capacity are treated as an error.
+func NewFixedBuilder(buffer []byte) *Builder {
+	return &Builder{
+		result:    buffer,
+		fixedSize: true,
+	}
+}
+
+// Bytes returns the bytes written by the builder or an error if one has
+// occurred during during building.
+func (b *Builder) Bytes() ([]byte, error) {
+	if b.err != nil {
+		return nil, b.err
+	}
+	return b.result[b.offset:], nil
+}
+
+// BytesOrPanic returns the bytes written by the builder or panics if an error
+// has occurred during building.
+func (b *Builder) BytesOrPanic() []byte {
+	if b.err != nil {
+		panic(b.err)
+	}
+	return b.result[b.offset:]
+}
+
+// AddUint8 appends an 8-bit value to the byte string.
+func (b *Builder) AddUint8(v uint8) {
+	b.add(byte(v))
+}
+
+// AddUint16 appends a big-endian, 16-bit value to the byte string.
+func (b *Builder) AddUint16(v uint16) {
+	b.add(byte(v>>8), byte(v))
+}
+
+// AddUint24 appends a big-endian, 24-bit value to the byte string. The highest
+// byte of the 32-bit input value is silently truncated.
+func (b *Builder) AddUint24(v uint32) {
+	b.add(byte(v>>16), byte(v>>8), byte(v))
+}
+
+// AddUint32 appends a big-endian, 32-bit value to the byte string.
+func (b *Builder) AddUint32(v uint32) {
+	b.add(byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
+}
+
+// AddBytes appends a sequence of bytes to the byte string.
+func (b *Builder) AddBytes(v []byte) {
+	b.add(v...)
+}
+
+// BuilderContinuation is continuation-passing interface for building
+// length-prefixed byte sequences. Builder methods for length-prefixed
+// sequences (AddUint8LengthPrefixed etc) will invoke the BuilderContinuation
+// supplied to them. The child builder passed to the continuation can be used
+// to build the content of the length-prefixed sequence. For example:
+//
+//   parent := cryptobyte.NewBuilder()
+//   parent.AddUint8LengthPrefixed(func (child *Builder) {
+//     child.AddUint8(42)
+//     child.AddUint8LengthPrefixed(func (grandchild *Builder) {
+//       grandchild.AddUint8(5)
+//     })
+//   })
+//
+// It is an error to write more bytes to the child than allowed by the reserved
+// length prefix. After the continuation returns, the child must be considered
+// invalid, i.e. users must not store any copies or references of the child
+// that outlive the continuation.
+//
+// If the continuation panics with a value of type BuildError then the inner
+// error will be returned as the error from Bytes. If the child panics
+// otherwise then Bytes will repanic with the same value.
+type BuilderContinuation func(child *Builder)
+
+// BuildError wraps an error. If a BuilderContinuation panics with this value,
+// the panic will be recovered and the inner error will be returned from
+// Builder.Bytes.
+type BuildError struct {
+	Err error
+}
+
+// AddUint8LengthPrefixed adds a 8-bit length-prefixed byte sequence.
+func (b *Builder) AddUint8LengthPrefixed(f BuilderContinuation) {
+	b.addLengthPrefixed(1, false, f)
+}
+
+// AddUint16LengthPrefixed adds a big-endian, 16-bit length-prefixed byte sequence.
+func (b *Builder) AddUint16LengthPrefixed(f BuilderContinuation) {
+	b.addLengthPrefixed(2, false, f)
+}
+
+// AddUint24LengthPrefixed adds a big-endian, 24-bit length-prefixed byte sequence.
+func (b *Builder) AddUint24LengthPrefixed(f BuilderContinuation) {
+	b.addLengthPrefixed(3, false, f)
+}
+
+// AddUint32LengthPrefixed adds a big-endian, 32-bit length-prefixed byte sequence.
+func (b *Builder) AddUint32LengthPrefixed(f BuilderContinuation) {
+	b.addLengthPrefixed(4, false, f)
+}
+
+func (b *Builder) callContinuation(f BuilderContinuation, arg *Builder) {
+	if !*b.inContinuation {
+		*b.inContinuation = true
+
+		defer func() {
+			*b.inContinuation = false
+
+			r := recover()
+			if r == nil {
+				return
+			}
+
+			if buildError, ok := r.(BuildError); ok {
+				b.err = buildError.Err
+			} else {
+				panic(r)
+			}
+		}()
+	}
+
+	f(arg)
+}
+
+func (b *Builder) addLengthPrefixed(lenLen int, isASN1 bool, f BuilderContinuation) {
+	// Subsequent writes can be ignored if the builder has encountered an error.
+	if b.err != nil {
+		return
+	}
+
+	offset := len(b.result)
+	b.add(make([]byte, lenLen)...)
+
+	if b.inContinuation == nil {
+		b.inContinuation = new(bool)
+	}
+
+	b.child = &Builder{
+		result:         b.result,
+		fixedSize:      b.fixedSize,
+		offset:         offset,
+		pendingLenLen:  lenLen,
+		pendingIsASN1:  isASN1,
+		inContinuation: b.inContinuation,
+	}
+
+	b.callContinuation(f, b.child)
+	b.flushChild()
+	if b.child != nil {
+		panic("cryptobyte: internal error")
+	}
+}
+
+func (b *Builder) flushChild() {
+	if b.child == nil {
+		return
+	}
+	b.child.flushChild()
+	child := b.child
+	b.child = nil
+
+	if child.err != nil {
+		b.err = child.err
+		return
+	}
+
+	length := len(child.result) - child.pendingLenLen - child.offset
+
+	if length < 0 {
+		panic("cryptobyte: internal error") // result unexpectedly shrunk
+	}
+
+	if child.pendingIsASN1 {
+		// For ASN.1, we reserved a single byte for the length. If that turned out
+		// to be incorrect, we have to move the contents along in order to make
+		// space.
+		if child.pendingLenLen != 1 {
+			panic("cryptobyte: internal error")
+		}
+		var lenLen, lenByte uint8
+		if int64(length) > 0xfffffffe {
+			b.err = errors.New("pending ASN.1 child too long")
+			return
+		} else if length > 0xffffff {
+			lenLen = 5
+			lenByte = 0x80 | 4
+		} else if length > 0xffff {
+			lenLen = 4
+			lenByte = 0x80 | 3
+		} else if length > 0xff {
+			lenLen = 3
+			lenByte = 0x80 | 2
+		} else if length > 0x7f {
+			lenLen = 2
+			lenByte = 0x80 | 1
+		} else {
+			lenLen = 1
+			lenByte = uint8(length)
+			length = 0
+		}
+
+		// Insert the initial length byte, make space for successive length bytes,
+		// and adjust the offset.
+		child.result[child.offset] = lenByte
+		extraBytes := int(lenLen - 1)
+		if extraBytes != 0 {
+			child.add(make([]byte, extraBytes)...)
+			childStart := child.offset + child.pendingLenLen
+			copy(child.result[childStart+extraBytes:], child.result[childStart:])
+		}
+		child.offset++
+		child.pendingLenLen = extraBytes
+	}
+
+	l := length
+	for i := child.pendingLenLen - 1; i >= 0; i-- {
+		child.result[child.offset+i] = uint8(l)
+		l >>= 8
+	}
+	if l != 0 {
+		b.err = fmt.Errorf("cryptobyte: pending child length %d exceeds %d-byte length prefix", length, child.pendingLenLen)
+		return
+	}
+
+	if !b.fixedSize {
+		b.result = child.result // In case child reallocated result.
+	}
+}
+
+func (b *Builder) add(bytes ...byte) {
+	if b.err != nil {
+		return
+	}
+	if b.child != nil {
+		panic("attempted write while child is pending")
+	}
+	if len(b.result)+len(bytes) < len(bytes) {
+		b.err = errors.New("cryptobyte: length overflow")
+	}
+	if b.fixedSize && len(b.result)+len(bytes) > cap(b.result) {
+		b.err = errors.New("cryptobyte: Builder is exceeding its fixed-size buffer")
+		return
+	}
+	b.result = append(b.result, bytes...)
+}
+
+// A MarshalingValue marshals itself into a Builder.
+type MarshalingValue interface {
+	// Marshal is called by Builder.AddValue. It receives a pointer to a builder
+	// to marshal itself into. It may return an error that occurred during
+	// marshaling, such as unset or invalid values.
+	Marshal(b *Builder) error
+}
+
+// AddValue calls Marshal on v, passing a pointer to the builder to append to.
+// If Marshal returns an error, it is set on the Builder so that subsequent
+// appends don't have an effect.
+func (b *Builder) AddValue(v MarshalingValue) {
+	err := v.Marshal(b)
+	if err != nil {
+		b.err = err
+	}
+}
diff --git a/vendor/golang.org/x/crypto/cryptobyte/string.go b/vendor/golang.org/x/crypto/cryptobyte/string.go
new file mode 100644
index 00000000..39bf98ae
--- /dev/null
+++ b/vendor/golang.org/x/crypto/cryptobyte/string.go
@@ -0,0 +1,166 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package cryptobyte contains types that help with parsing and constructing
+// length-prefixed, binary messages, including ASN.1 DER. (The asn1 subpackage
+// contains useful ASN.1 constants.)
+//
+// The String type is for parsing. It wraps a []byte slice and provides helper
+// functions for consuming structures, value by value.
+//
+// The Builder type is for constructing messages. It providers helper functions
+// for appending values and also for appending length-prefixed submessages –
+// without having to worry about calculating the length prefix ahead of time.
+//
+// See the documentation and examples for the Builder and String types to get
+// started.
+package cryptobyte // import "golang.org/x/crypto/cryptobyte"
+
+// String represents a string of bytes. It provides methods for parsing
+// fixed-length and length-prefixed values from it.
+type String []byte
+
+// read advances a String by n bytes and returns them. If less than n bytes
+// remain, it returns nil.
+func (s *String) read(n int) []byte {
+	if len(*s) < n {
+		return nil
+	}
+	v := (*s)[:n]
+	*s = (*s)[n:]
+	return v
+}
+
+// Skip advances the String by n byte and reports whether it was successful.
+func (s *String) Skip(n int) bool {
+	return s.read(n) != nil
+}
+
+// ReadUint8 decodes an 8-bit value into out and advances over it.
+// It reports whether the read was successful.
+func (s *String) ReadUint8(out *uint8) bool {
+	v := s.read(1)
+	if v == nil {
+		return false
+	}
+	*out = uint8(v[0])
+	return true
+}
+
+// ReadUint16 decodes a big-endian, 16-bit value into out and advances over it.
+// It reports whether the read was successful.
+func (s *String) ReadUint16(out *uint16) bool {
+	v := s.read(2)
+	if v == nil {
+		return false
+	}
+	*out = uint16(v[0])<<8 | uint16(v[1])
+	return true
+}
+
+// ReadUint24 decodes a big-endian, 24-bit value into out and advances over it.
+// It reports whether the read was successful.
+func (s *String) ReadUint24(out *uint32) bool {
+	v := s.read(3)
+	if v == nil {
+		return false
+	}
+	*out = uint32(v[0])<<16 | uint32(v[1])<<8 | uint32(v[2])
+	return true
+}
+
+// ReadUint32 decodes a big-endian, 32-bit value into out and advances over it.
+// It reports whether the read was successful.
+func (s *String) ReadUint32(out *uint32) bool {
+	v := s.read(4)
+	if v == nil {
+		return false
+	}
+	*out = uint32(v[0])<<24 | uint32(v[1])<<16 | uint32(v[2])<<8 | uint32(v[3])
+	return true
+}
+
+func (s *String) readUnsigned(out *uint32, length int) bool {
+	v := s.read(length)
+	if v == nil {
+		return false
+	}
+	var result uint32
+	for i := 0; i < length; i++ {
+		result <<= 8
+		result |= uint32(v[i])
+	}
+	*out = result
+	return true
+}
+
+func (s *String) readLengthPrefixed(lenLen int, outChild *String) bool {
+	lenBytes := s.read(lenLen)
+	if lenBytes == nil {
+		return false
+	}
+	var length uint32
+	for _, b := range lenBytes {
+		length = length << 8
+		length = length | uint32(b)
+	}
+	if int(length) < 0 {
+		// This currently cannot overflow because we read uint24 at most, but check
+		// anyway in case that changes in the future.
+		return false
+	}
+	v := s.read(int(length))
+	if v == nil {
+		return false
+	}
+	*outChild = v
+	return true
+}
+
+// ReadUint8LengthPrefixed reads the content of an 8-bit length-prefixed value
+// into out and advances over it. It reports whether the read was successful.
+func (s *String) ReadUint8LengthPrefixed(out *String) bool {
+	return s.readLengthPrefixed(1, out)
+}
+
+// ReadUint16LengthPrefixed reads the content of a big-endian, 16-bit
+// length-prefixed value into out and advances over it. It reports whether the
+// read was successful.
+func (s *String) ReadUint16LengthPrefixed(out *String) bool {
+	return s.readLengthPrefixed(2, out)
+}
+
+// ReadUint24LengthPrefixed reads the content of a big-endian, 24-bit
+// length-prefixed value into out and advances over it. It reports whether
+// the read was successful.
+func (s *String) ReadUint24LengthPrefixed(out *String) bool {
+	return s.readLengthPrefixed(3, out)
+}
+
+// ReadBytes reads n bytes into out and advances over them. It reports
+// whether the read was successful.
+func (s *String) ReadBytes(out *[]byte, n int) bool {
+	v := s.read(n)
+	if v == nil {
+		return false
+	}
+	*out = v
+	return true
+}
+
+// CopyBytes copies len(out) bytes into out and advances over them. It reports
+// whether the copy operation was successful
+func (s *String) CopyBytes(out []byte) bool {
+	n := len(out)
+	v := s.read(n)
+	if v == nil {
+		return false
+	}
+	return copy(out, v) == n
+}
+
+// Empty reports whether the string does not contain any bytes.
+func (s String) Empty() bool {
+	return len(s) == 0
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index a4d329a6..edaf307f 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -8,10 +8,12 @@ github.com/sirupsen/logrus
 github.com/weppos/publicsuffix-go/publicsuffix
 # github.com/zmap/zcrypto v0.0.0-20190329181646-dff83107394d
 github.com/zmap/zcrypto/x509
+github.com/zmap/zcrypto/x509/ct
 github.com/zmap/zcrypto/x509/pkix
 github.com/zmap/zcrypto/json
-github.com/zmap/zcrypto/x509/ct
 # golang.org/x/crypto v0.0.0-20180904163835-0709b304e793
+golang.org/x/crypto/cryptobyte
+golang.org/x/crypto/cryptobyte/asn1
 golang.org/x/crypto/ssh/terminal
 # golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95
 golang.org/x/net/idna


Test patch:
diff --git a/lints/lint_spki_rsa_encryption_parameter_not_null_test.go b/lints/lint_spki_rsa_encryption_parameter_not_null_test.go
new file mode 100644
index 00000000..52b41400
--- /dev/null
+++ b/lints/lint_spki_rsa_encryption_parameter_not_null_test.go
@@ -0,0 +1,48 @@
+package lints
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestRSAAlgIDNullParams(t *testing.T) {
+
+	testCases := []struct {
+		name           string
+		filepath       string
+		expectedStatus LintStatus
+		details        string
+	}{
+		{
+			name:           "pass cert with NULL params",
+			filepath:       "rsawithsha1after2016.pem",
+			expectedStatus: Pass,
+		},
+		{
+			name:           "error cert with missing NULL params",
+			filepath:       "rsaAlgIDNoNULLParams.pem",
+			expectedStatus: Error,
+			details:        "certificate pkixPublicKey RSA algorithm identifier missing required NULL parameter",
+		},
+		{
+			name:           "error cert with non NULL params",
+			filepath:       "rsaKeyWithParameters.pem",
+			expectedStatus: Error,
+			details:        "certificate pkixPublicKey RSA algorithm identifier with non-NULL parameter",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			inputPath := fmt.Sprintf("%s%s", testCaseDir, tc.filepath)
+			result := Lints["e_spki_rsa_encryption_parameter_not_null"].Execute(ReadCertificate(inputPath))
+			if result.Status != tc.expectedStatus {
+				t.Errorf("expected result %v was %v", tc.expectedStatus, result.Status)
+			}
+
+			if result.Details != tc.details {
+				t.Errorf("expected error details %q was %q", tc.details, result.Details)
+			}
+		})
+	}
+}
diff --git a/lints/lint_tbs_signature_rsa_encryption_parameter_not_null_test.go b/lints/lint_tbs_signature_rsa_encryption_parameter_not_null_test.go
new file mode 100644
index 00000000..df11cdec
--- /dev/null
+++ b/lints/lint_tbs_signature_rsa_encryption_parameter_not_null_test.go
@@ -0,0 +1,42 @@
+package lints
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestSigRSAAlgIDNullParams(t *testing.T) {
+
+	testCases := []struct {
+		name           string
+		filepath       string
+		expectedStatus LintStatus
+		details        string
+	}{
+		{
+			name:           "pass cert with NULL params",
+			filepath:       "rsawithsha1after2016.pem",
+			expectedStatus: Pass,
+		},
+		{
+			name:           "error cert with missing NULL params",
+			filepath:       "rsaSigAlgoNoNULLParam.pem",
+			expectedStatus: Error,
+			details:        "certificate tbsCertificate.signature RSA algorithm identifier missing required NULL parameter",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			inputPath := fmt.Sprintf("%s%s", testCaseDir, tc.filepath)
+			result := Lints["e_tbs_signature_rsa_encryption_parameter_not_null"].Execute(ReadCertificate(inputPath))
+			if result.Status != tc.expectedStatus {
+				t.Errorf("expected result %v was %v", tc.expectedStatus, result.Status)
+			}
+
+			if result.Details != tc.details {
+				t.Errorf("expected error details %q was %q", tc.details, result.Details)
+			}
+		})
+	}
+}
diff --git a/testlint/testCerts/rsaAlgIDNoNULLParams.pem b/testlint/testCerts/rsaAlgIDNoNULLParams.pem
new file mode 100644
index 00000000..a2eb7e95
--- /dev/null
+++ b/testlint/testCerts/rsaAlgIDNoNULLParams.pem
@@ -0,0 +1,29 @@
+-----BEGIN CERTIFICATE-----
+MIIFDDCCA/SgAwIBAgIMLdcgkQWz0AYwAS3CMA0GCSqGSIb3DQEBCwUAMEwxCzAJ
+BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSIwIAYDVQQDExlB
+bHBoYVNTTCBDQSAtIFNIQTI1NiAtIEcyMB4XDTE4MTIxMDExMDczM1oXDTIwMDEy
+OTEzMzQxNVowTzELMAkGA1UEBhMCTkwxITAfBgNVBAsTGERvbWFpbiBDb250cm9s
+IFZhbGlkYXRlZDEdMBsGA1UEAxMUd3d3LnNob3JlYXJjaGllZi5jb20wggEgMAsG
+CSqGSIb3DQEBAQOCAQ8AMIIBCgKCAQEAtdAllnmhxoule45cByFJjIuYiRe5YllK
+0RWYZ8Q9wehjlYTCde5fOtcMigtrYCUheB7Mf173m+xrp8II23WfNK7CIKlQUwuN
++blPp+lvxhEQF5p+KaAt0OPnm5WKc67Dwxy6rwztN4O6+mBJpluKYFNHET8auks8
+22aSFH3bvagLebUWxTINxl6RfeM0W7bfZDC8i+DaRfxGrgjgz3pko7og/qy4Dp0r
+MvFWYSPCT/MspXR3KOrWLrqY+Woa8e2ZDz5TymEYzQPF7hJa7KiZMQKHyOv7Eygt
+d7bfiKMt0RH+v+rj0Y7uUSxdL/gwFbsYwCzhQw/5fGFjHxLvVzhkBwIDAQABo4IB
+6zCCAecwDgYDVR0PAQH/BAQDAgWgMIGJBggrBgEFBQcBAQR9MHswQgYIKwYBBQUH
+MAKGNmh0dHA6Ly9zZWN1cmUyLmFscGhhc3NsLmNvbS9jYWNlcnQvZ3NhbHBoYXNo
+YTJnMnIxLmNydDA1BggrBgEFBQcwAYYpaHR0cDovL29jc3AyLmdsb2JhbHNpZ24u
+Y29tL2dzYWxwaGFzaGEyZzIwVwYDVR0gBFAwTjBCBgorBgEEAaAyAQoKMDQwMgYI
+KwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkv
+MAgGBmeBDAECATAJBgNVHRMEAjAAMD4GA1UdHwQ3MDUwM6AxoC+GLWh0dHA6Ly9j
+cmwyLmFscGhhc3NsLmNvbS9ncy9nc2FscGhhc2hhMmcyLmNybDAxBgNVHREEKjAo
+ghR3d3cuc2hvcmVhcmNoaWVmLmNvbYIQc2hvcmVhcmNoaWVmLmNvbTAdBgNVHSUE
+FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFJhFov73PPyNAO6fiTaV
+WaoUnlltMB8GA1UdIwQYMBaAFPXN1TwIUPlqTzq3l9pWg+Zp0mj3MBMGCisGAQQB
+1nkCBAMBAf8EAgUAMA0GCSqGSIb3DQEBCwUAA4IBAQAeV2oaeX5sBPUUzBRoa4Mp
+foZ8icAdzezy/Z1DEE+GmMqAya1RFL2DCMk2rPX133YHpC3pWkDMdl6gmrz5KOD/
+080eUIs/VE9unD1zUPvHTA9a+Bokz/Rprqj9vq0VUuGI1Cp7xlYxs+gAv0ZTNchg
+sG7Fbuwz962KZAUBlzn/x0c+vXmKcz0sQJdtb2nn+vupqR0rCP0NAhKdNMaRxgOE
+ZuZj1IAogNwBeNUVcIaGKxM4sOOwdB/DjMUvTHn0whTTr17ggAPYb33w7VN7m0uN
+s5RhHGQnAXdvLGOSkeqBWqYdsnNJiFvyS3cU6+Psd6MDUeKVNA0mPyYW4pbhLfAB
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/testlint/testCerts/rsaKeyWithParameters.pem b/testlint/testCerts/rsaKeyWithParameters.pem
new file mode 100644
index 00000000..edfe49c4
--- /dev/null
+++ b/testlint/testCerts/rsaKeyWithParameters.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDJzCCAg+gAwIBAgICBnUwDQYJKoZIhvcNAQELBQAwIzEhMB8GA1UEAxMYUlNB
+IGNlcnQgd2l0aCBrZXkgcGFyYW1zMB4XDTE5MDUzMTIyNDU1OVoXDTI5MDUzMTIy
+NDU1OVowIzEhMB8GA1UEAxMYUlNBIGNlcnQgd2l0aCBrZXkgcGFyYW1zMIIBVjBB
+BgkqhkiG9w0BAQEwNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0G
+CWCGSAFlAwQCAQUAogMCAUADggEPADCCAQoCggEBALbwKrmX7z4vbfjKX2FI+z6v
+Zt6QWT0a2c2oGS8hBw9BLWkV1MNgE6o0vXufWHaek5zv8P4M5ln6BybszPARyc8A
+PCC2QXL9WnlwmGyG01uR+LfUjIHHQf+fgR7ITqM66E7rxKNhRZiDkkmzRSp1tAV6
+9SPAR3NmFNYeUXJAf4CAYEZu+FbCEU/nH7HEghh3RXBuE/jxaGz4v8cHn+IFyQIb
+DH2MR1mBn4mlud3vn44QIsuv+P616M2VLWoMhNklVu3GnAYvopyVAUBVoyTfI4b5
+B30OSHCaftKsP6Bfi3+6kgPiIObHjTgOW04LQAlMQTx/7UCL0H1000MmkA9UqHcC
+AwEAAaMxMC8wDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr
+BgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEAbPduaXPBi81YUG04xmhwX6K8jR5w
+3Jtx4Qogb1DP05L3CilS/vnNbpqKlOWOZeiXGSuEGnjMNyg2gdONh01JnDqSF3UF
+/9z1bazsiH1Y/vrr1Olcj3GEvcGM7EpwobrWWarLVWEbdjS7JNOwve547KXpUBM2
+hbtJNIi/oZEFtlrvHSNWDF/tb3pslwjVhrd/3iQ61DUcnzCIaQdUs/9ftt3GilSo
+VZRq2rFyb7b3Wdp43w9QksLyKEHbayz7ITgbVTWkeKGbx6Gob2Zz2y2rWSqoD+7z
+03JmjpqVdhp9WZwAB7pxMeSOVVDKtsBn03koUNy7DnuzBstEDQLthTJYjg==
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/testlint/testCerts/rsaSigAlgoNoNULLParam.pem b/testlint/testCerts/rsaSigAlgoNoNULLParam.pem
new file mode 100644
index 00000000..af9dab02
--- /dev/null
+++ b/testlint/testCerts/rsaSigAlgoNoNULLParam.pem
@@ -0,0 +1,67 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            fb:b0:4c:2e:ab:10:9b:0c
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd
+        Validity
+            Not Before: Apr 23 20:50:40 2014 GMT
+            Not After : Apr 22 20:50:40 2017 GMT
+        Subject: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (1024 bit)
+                Modulus:
+                    00:d8:2b:c8:a6:32:e4:62:ff:4d:f3:d0:ad:59:8b:
+                    45:a7:bd:f1:47:bf:09:58:7b:22:bd:35:ae:97:25:
+                    86:94:a0:80:c0:b4:1f:76:91:67:46:31:d0:10:84:
+                    b7:22:1e:70:23:91:72:c8:e9:6d:79:3a:85:77:80:
+                    0f:c4:95:16:75:c5:4a:71:4c:c8:63:3f:a3:f2:63:
+                    9c:2a:4f:9a:fa:cb:c1:71:6e:28:85:28:a0:27:1e:
+                    65:1c:ae:07:d5:5b:6f:2d:43:ed:2b:90:b1:8c:af:
+                    24:6d:ae:e9:17:3a:05:c1:bf:b8:1c:ae:65:3b:1b:
+                    58:c2:d9:ae:d6:aa:67:88:f1
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                8B:75:D5:AC:CB:08:BE:0E:1F:65:B7:FA:56:BE:6C:A7:75:DA:85:AF
+            X509v3 Authority Key Identifier: 
+                keyid:8B:75:D5:AC:CB:08:BE:0E:1F:65:B7:FA:56:BE:6C:A7:75:DA:85:AF
+
+            X509v3 Basic Constraints: 
+                CA:TRUE
+    Signature Algorithm: sha256WithRSAEncryption
+         a7:40:18:92:cb:86:a7:cb:4f:de:3e:d8:3a:51:f5:a3:18:89:
+         62:4d:c8:26:f8:43:71:f4:ae:85:82:1c:39:86:6d:02:14:32:
+         d3:6b:80:44:7e:31:47:16:0c:c4:e7:d4:ef:eb:82:f2:04:d5:
+         6e:e5:f0:98:cc:47:92:93:8f:3e:2c:ae:30:a4:23:39:20:4f:
+         7d:87:0b:52:41:ad:79:97:cf:36:3c:df:41:1b:a9:91:87:a8:
+         4b:44:91:83:04:4c:79:2e:0c:2a:cf:93:2f:96:2a:cc:2f:a0:
+         4e:92:70:7a:03:d1:4f:d1:94:c8:0d:a8:5a:c0:9d:26:ae:f1:
+         d4:bc:6d:fd:95:bb:fb:c0:ed:8f:8e:a5:b3:b6:e3:08:26:c1:
+         99:e7:d4:54:41:23:49:d4:b3:61:52:3b:a9:c3:fd:3f:ea:54:
+         05:69:9c:0f:2c:5a:6c:68:2c:3e:2f:f8:79:2a:d8:b5:de:4f:
+         19:2e:57:17:bb:ea:77:25:c7:45:6f:3c:76:ab:03:3f:fa:e9:
+         99:1d:2d:f7:99:d7:f2:e1:0e:5c:7d:3d:52:b3:96:fc:9e:de:
+         b1:37:8e:87:a1:c5:95:ae:fa:75:9a:7e:3c:4c:28:dc:f1:7b:
+         66:35:fa:67:01:46:04:d4:df:dc:be:13:f9:4e:47:9b:ff:88:
+         7d:a8:53:be
+-----BEGIN CERTIFICATE-----
+MIIC1zCCAb+gAwIBAgIJAPuwTC6rEJsMMAsGCSqGSIb3DQEBCzBFMQswCQYDVQQG
+EwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lk
+Z2l0cyBQdHkgTHRkMB4XDTE0MDQyMzIwNTA0MFoXDTE3MDQyMjIwNTA0MFowRTEL
+MAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVy
+bmV0IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
+2CvIpjLkYv9N89CtWYtFp73xR78JWHsivTWulyWGlKCAwLQfdpFnRjHQEIS3Ih5w
+I5FyyOlteTqFd4APxJUWdcVKcUzIYz+j8mOcKk+a+svBcW4ohSigJx5lHK4H1Vtv
+LUPtK5CxjK8kba7pFzoFwb+4HK5lOxtYwtmu1qpniPECAwEAAaNQME4wHQYDVR0O
+BBYEFIt11azLCL4OH2W3+la+bKd12oWvMB8GA1UdIwQYMBaAFIt11azLCL4OH2W3
++la+bKd12oWvMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKdAGJLL
+hqfLT94+2DpR9aMYiWJNyCb4Q3H0roWCHDmGbQIUMtNrgER+MUcWDMTn1O/rgvIE
+1W7l8JjMR5KTjz4srjCkIzkgT32HC1JBrXmXzzY830EbqZGHqEtEkYMETHkuDCrP
+ky+WKswvoE6ScHoD0U/RlMgNqFrAnSau8dS8bf2Vu/vA7Y+OpbO24wgmwZnn1FRB
+I0nUs2FSO6nD/T/qVAVpnA8sWmxoLD4v+Hkq2LXeTxkuVxe76nclx0VvPHarAz/6
+6ZkdLfeZ1/LhDlx9PVKzlvye3rE3joehxZWu+nWafjxMKNzxe2Y1+mcBRgTU39y+
+E/lOR5v/iH2oU74=
+-----END CERTIFICATE-----
diff --git a/util/algorithm_identifier_test.go b/util/algorithm_identifier_test.go
new file mode 100644
index 00000000..3f86c98b
--- /dev/null
+++ b/util/algorithm_identifier_test.go
@@ -0,0 +1,133 @@
+package util
+
+import (
+	"encoding/asn1"
+	"encoding/base64"
+	"testing"
+)
+
+func TestCheckAlgorithmIDParamNotNULL(t *testing.T) {
+
+	testCases := []struct {
+		name      string
+		checkOID  asn1.ObjectIdentifier
+		algorithm string
+		errStr    string
+	}{
+		{
+			name:      "valid rsaEncryption",
+			checkOID:  OidRSAEncryption,
+			algorithm: "MA0GCSqGSIb3DQEBAQUA",
+			errStr:    "",
+		},
+		{
+			name:      "valid md2WithRSAEncryption",
+			checkOID:  OidMD2WithRSAEncryption,
+			algorithm: "MA0GCSqGSIb3DQEBAgUA",
+			errStr:    "",
+		},
+		{
+			name:      "valid md5WithRSAEncryption",
+			checkOID:  OidMD5WithRSAEncryption,
+			algorithm: "MA0GCSqGSIb3DQEBBAUA",
+			errStr:    "",
+		},
+		{
+			name:      "valid sha-1WithRSAEncryption",
+			checkOID:  OidSHA1WithRSAEncryption,
+			algorithm: "MA0GCSqGSIb3DQEBBQUA",
+			errStr:    "",
+		},
+		{
+			name:      "valid sha224WithRSAEncryption",
+			checkOID:  OidSHA224WithRSAEncryption,
+			algorithm: "MA0GCSqGSIb3DQEBDgUA",
+			errStr:    "",
+		},
+		{
+			name:      "valid sha256WithRSAEncryption",
+			checkOID:  OidSHA256WithRSAEncryption,
+			algorithm: "MA0GCSqGSIb3DQEBCwUA",
+			errStr:    "",
+		},
+		{
+			name:      "valid sha384WithRSAEncryption",
+			checkOID:  OidSHA384WithRSAEncryption,
+			algorithm: "MA0GCSqGSIb3DQEBDAUA",
+			errStr:    "",
+		},
+		{
+			name:      "valid sha512WithRSAEncryption",
+			checkOID:  OidSHA512WithRSAEncryption,
+			algorithm: "MA0GCSqGSIb3DQEBDQUA",
+			errStr:    "",
+		},
+
+		{
+			name:      "extra field in algorithm sequence",
+			checkOID:  OidRSAEncryption,
+			algorithm: "MA8GCSqGSIb3DQEBAQUAAgA=",
+			errStr:    "RSA algorithm identifier with trailing data",
+		},
+		{
+			name:      "missing NULL param",
+			checkOID:  OidRSAEncryption,
+			algorithm: "MAsGCSqGSIb3DQEBAQ==",
+			errStr:    "RSA algorithm identifier missing required NULL parameter",
+		},
+		{
+			name:      "NULL param containing data",
+			checkOID:  OidRSAEncryption,
+			algorithm: "MBQGCSqGSIb3DQEBAQUHTk9UTlVMTA==",
+			errStr:    "RSA algorithm identifier with NULL parameter containing data",
+		},
+		{
+			name:      "trailing data after NULL param",
+			checkOID:  OidRSAEncryption,
+			algorithm: "MBQGCSqGSIb3DQEBAQUATk9UTlVMTA==",
+			errStr:    "RSA algorithm identifier with trailing data",
+		},
+		{
+			name:      "context-specific 0 tag in param",
+			checkOID:  OidRSAEncryption,
+			algorithm: "MA0GCSqGSIb3DQEBAaAA",
+			errStr:    "RSA algorithm identifier with non-NULL parameter",
+		},
+		{
+			name:      "wrong algorithm oid",
+			algorithm: "MBQGCSqGSIb3DQEBAgUATk9UTlVMTA==",
+			errStr:    "error algorithmID to check is not RSA",
+		},
+		{
+			name:      "malformed algorithm sequence",
+			checkOID:  OidRSAEncryption,
+			algorithm: "MQ0GCSqGSIb3DQEBAQU",
+			errStr:    "error reading algorithm",
+		},
+		{
+			name:      "malformed OID",
+			checkOID:  OidRSAEncryption,
+			algorithm: "MBgTFDEuMi44NDAuMTEzNTQ5LjEuMS4xBQA=",
+			errStr:    "error reading algorithm OID",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			algoBytes, _ := base64.StdEncoding.DecodeString(tc.algorithm)
+
+			err := CheckAlgorithmIDParamNotNULL(algoBytes, tc.checkOID)
+			if err == nil {
+				if tc.errStr != "" {
+					t.Errorf("expected error %v was no error", tc.errStr)
+				}
+
+				return
+			}
+
+			if err.Error() != tc.errStr {
+				t.Errorf("expected error %q was %q", tc.errStr, err.Error())
+			}
+		})
+	}
+}