-
Notifications
You must be signed in to change notification settings - Fork 130
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
x509util.CreateCertificateRequest wraps x509.CreateCertificateRequest and adds support for challengePassword attribute. Adding a new attribute means re-signing the whole CSR, which means importing private methods and types from the x509 package. I hope to eventually submit a CL to the stdlib. Go Issue: golang/go#15995 For #44. For #22.
- Loading branch information
Showing
3 changed files
with
400 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// package x509 provides utilities for working with x509 types. | ||
package x509util |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,348 @@ | ||
/* | ||
Copyright (c) 2009 The Go Authors. All rights reserved. | ||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are | ||
met: | ||
* Redistributions of source code must retain the above copyright | ||
notice, this list of conditions and the following disclaimer. | ||
* Redistributions in binary form must reproduce the above | ||
copyright notice, this list of conditions and the following disclaimer | ||
in the documentation and/or other materials provided with the | ||
distribution. | ||
* Neither the name of Google Inc. nor the names of its | ||
contributors may be used to endorse or promote products derived from | ||
this software without specific prior written permission. | ||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
*/ | ||
|
||
package x509util | ||
|
||
import ( | ||
"crypto" | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"crypto/rsa" | ||
_ "crypto/sha1" | ||
_ "crypto/sha256" | ||
_ "crypto/sha512" | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
"encoding/asn1" | ||
"errors" | ||
"io" | ||
) | ||
|
||
type CertificateRequest struct { | ||
x509.CertificateRequest | ||
|
||
ChallengePassword string | ||
} | ||
|
||
// CreateCertificateRequest creates a new certificate request based on a template. | ||
// The resulting CSR is similar to x509 but optionally supports the | ||
// challengePassword attribute. | ||
// | ||
// See https://github.com/golang/go/issues/15995 | ||
func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv interface{}) (csr []byte, err error) { | ||
if template.ChallengePassword == "" { | ||
// if no challenge password, return a stdlib CSR. | ||
return x509.CreateCertificateRequest(rand, &template.CertificateRequest, priv) | ||
} | ||
derBytes, err := x509.CreateCertificateRequest(rand, &template.CertificateRequest, priv) | ||
if err != nil { | ||
return nil, err | ||
} | ||
// add the challenge attribute to the CSR, then re-sign the raw csr. | ||
// not checking the crypto.Signer assertion because x509.CreateCertificateRequest already did that. | ||
return addChallenge( | ||
template.CertificateRequest.SignatureAlgorithm, | ||
rand, | ||
derBytes, | ||
template.ChallengePassword, | ||
priv.(crypto.Signer), | ||
) | ||
} | ||
|
||
type passwordChallengeAttribute struct { | ||
Type asn1.ObjectIdentifier | ||
Value []string `asn1:"set"` | ||
} | ||
|
||
// The structures below are copied from the Go standard library x509 package. | ||
|
||
type publicKeyInfo struct { | ||
Raw asn1.RawContent | ||
Algorithm pkix.AlgorithmIdentifier | ||
PublicKey asn1.BitString | ||
} | ||
|
||
type tbsCertificateRequest struct { | ||
Raw asn1.RawContent | ||
Version int | ||
Subject asn1.RawValue | ||
PublicKey publicKeyInfo | ||
RawAttributes []asn1.RawValue `asn1:"tag:0"` | ||
} | ||
|
||
type certificateRequest struct { | ||
Raw asn1.RawContent | ||
TBSCSR tbsCertificateRequest | ||
SignatureAlgorithm pkix.AlgorithmIdentifier | ||
SignatureValue asn1.BitString | ||
} | ||
|
||
// ParseChallengePassword extracts the challengePassword attribute from a | ||
// DER encoded Certificate Signing Request. | ||
func ParseChallengePassword(asn1Data []byte) (string, error) { | ||
type attribute struct { | ||
ID asn1.ObjectIdentifier | ||
Value asn1.RawValue `asn1:"set"` | ||
} | ||
var csr certificateRequest | ||
rest, err := asn1.Unmarshal(asn1Data, &csr) | ||
if err != nil { | ||
return "", err | ||
} else if len(rest) != 0 { | ||
err = asn1.SyntaxError{Msg: "trailing data"} | ||
return "", err | ||
} | ||
|
||
var password string | ||
for _, rawAttr := range csr.TBSCSR.RawAttributes { | ||
var attr attribute | ||
_, err := asn1.Unmarshal(rawAttr.FullBytes, &attr) | ||
if err != nil { | ||
return "", err | ||
} | ||
if attr.ID.Equal(oidChallengePassword) { | ||
_, err := asn1.Unmarshal(attr.Value.Bytes, &password) | ||
if err != nil { | ||
return "", err | ||
} | ||
} | ||
} | ||
|
||
return password, nil | ||
} | ||
|
||
// addChallenge takes a raw CSR created by x509.CreateCertificateRequest, | ||
// adds a passwordChallengeAttribute and re-signs the raw CSR bytes. | ||
func addChallenge( | ||
templateSigAlgo x509.SignatureAlgorithm, | ||
reader io.Reader, | ||
derBytes []byte, | ||
challenge string, | ||
key crypto.Signer, | ||
) (csr []byte, err error) { | ||
var hashFunc crypto.Hash | ||
var sigAlgo pkix.AlgorithmIdentifier | ||
hashFunc, sigAlgo, err = signingParamsForPublicKey(key.Public(), templateSigAlgo) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var req certificateRequest | ||
rest, err := asn1.Unmarshal(derBytes, &req) | ||
if err != nil { | ||
return nil, err | ||
} else if len(rest) != 0 { | ||
err = asn1.SyntaxError{Msg: "trailing data"} | ||
return nil, err | ||
} | ||
|
||
passwordAttribute := passwordChallengeAttribute{ | ||
Type: oidChallengePassword, | ||
Value: []string{challenge}, | ||
} | ||
b, err := asn1.Marshal(passwordAttribute) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var rawAttribute asn1.RawValue | ||
rest, err = asn1.Unmarshal(b, &rawAttribute) | ||
if err != nil { | ||
return nil, err | ||
} else if len(rest) != 0 { | ||
err = asn1.SyntaxError{Msg: "trailing data"} | ||
return nil, err | ||
} | ||
|
||
// append attribute | ||
req.TBSCSR.RawAttributes = append(req.TBSCSR.RawAttributes, rawAttribute) | ||
|
||
// recreate request | ||
tbsCSR := tbsCertificateRequest{ | ||
Version: 0, | ||
Subject: req.TBSCSR.Subject, | ||
PublicKey: req.TBSCSR.PublicKey, | ||
RawAttributes: req.TBSCSR.RawAttributes, | ||
} | ||
|
||
tbsCSRContents, err := asn1.Marshal(tbsCSR) | ||
if err != nil { | ||
return nil, err | ||
} | ||
tbsCSR.Raw = tbsCSRContents | ||
|
||
h := hashFunc.New() | ||
if _, err := h.Write(tbsCSRContents); err != nil { | ||
return nil, err | ||
} | ||
|
||
var signature []byte | ||
signature, err = key.Sign(reader, h.Sum(nil), hashFunc) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return asn1.Marshal(certificateRequest{ | ||
TBSCSR: tbsCSR, | ||
SignatureAlgorithm: sigAlgo, | ||
SignatureValue: asn1.BitString{ | ||
Bytes: signature, | ||
BitLength: len(signature) * 8, | ||
}, | ||
}) | ||
} | ||
|
||
// signingParamsForPublicKey returns the parameters to use for signing with | ||
// priv. If requestedSigAlgo is not zero then it overrides the default | ||
// signature algorithm. | ||
func signingParamsForPublicKey(pub interface{}, requestedSigAlgo x509.SignatureAlgorithm) (hashFunc crypto.Hash, sigAlgo pkix.AlgorithmIdentifier, err error) { | ||
var pubType x509.PublicKeyAlgorithm | ||
|
||
switch pub := pub.(type) { | ||
case *rsa.PublicKey: | ||
pubType = x509.RSA | ||
hashFunc = crypto.SHA256 | ||
sigAlgo.Algorithm = oidSignatureSHA256WithRSA | ||
sigAlgo.Parameters = asn1NullRawValue | ||
|
||
case *ecdsa.PublicKey: | ||
pubType = x509.ECDSA | ||
|
||
switch pub.Curve { | ||
case elliptic.P224(), elliptic.P256(): | ||
hashFunc = crypto.SHA256 | ||
sigAlgo.Algorithm = oidSignatureECDSAWithSHA256 | ||
case elliptic.P384(): | ||
hashFunc = crypto.SHA384 | ||
sigAlgo.Algorithm = oidSignatureECDSAWithSHA384 | ||
case elliptic.P521(): | ||
hashFunc = crypto.SHA512 | ||
sigAlgo.Algorithm = oidSignatureECDSAWithSHA512 | ||
default: | ||
err = errors.New("x509: unknown elliptic curve") | ||
} | ||
|
||
default: | ||
err = errors.New("x509: only RSA and ECDSA keys supported") | ||
} | ||
|
||
if err != nil { | ||
return | ||
} | ||
|
||
if requestedSigAlgo == 0 { | ||
return | ||
} | ||
|
||
found := false | ||
for _, details := range signatureAlgorithmDetails { | ||
if details.algo == requestedSigAlgo { | ||
if details.pubKeyAlgo != pubType { | ||
err = errors.New("x509: requested SignatureAlgorithm does not match private key type") | ||
return | ||
} | ||
sigAlgo.Algorithm, hashFunc = details.oid, details.hash | ||
if hashFunc == 0 { | ||
err = errors.New("x509: cannot sign with hash function requested") | ||
return | ||
} | ||
// TODO add this? | ||
// if requestedSigAlgo.isRSAPSS() { | ||
// sigAlgo.Parameters = rsaPSSParameters(hashFunc) | ||
// } | ||
found = true | ||
break | ||
} | ||
} | ||
|
||
if !found { | ||
err = errors.New("x509: unknown SignatureAlgorithm") | ||
} | ||
|
||
return | ||
} | ||
|
||
var signatureAlgorithmDetails = []struct { | ||
algo x509.SignatureAlgorithm | ||
oid asn1.ObjectIdentifier | ||
pubKeyAlgo x509.PublicKeyAlgorithm | ||
hash crypto.Hash | ||
}{ | ||
{x509.MD2WithRSA, oidSignatureMD2WithRSA, x509.RSA, crypto.Hash(0) /* no value for MD2 */}, | ||
{x509.MD5WithRSA, oidSignatureMD5WithRSA, x509.RSA, crypto.MD5}, | ||
{x509.SHA1WithRSA, oidSignatureSHA1WithRSA, x509.RSA, crypto.SHA1}, | ||
{x509.SHA1WithRSA, oidISOSignatureSHA1WithRSA, x509.RSA, crypto.SHA1}, | ||
{x509.SHA256WithRSA, oidSignatureSHA256WithRSA, x509.RSA, crypto.SHA256}, | ||
{x509.SHA384WithRSA, oidSignatureSHA384WithRSA, x509.RSA, crypto.SHA384}, | ||
{x509.SHA512WithRSA, oidSignatureSHA512WithRSA, x509.RSA, crypto.SHA512}, | ||
{x509.SHA256WithRSAPSS, oidSignatureRSAPSS, x509.RSA, crypto.SHA256}, | ||
{x509.SHA384WithRSAPSS, oidSignatureRSAPSS, x509.RSA, crypto.SHA384}, | ||
{x509.SHA512WithRSAPSS, oidSignatureRSAPSS, x509.RSA, crypto.SHA512}, | ||
{x509.DSAWithSHA1, oidSignatureDSAWithSHA1, x509.DSA, crypto.SHA1}, | ||
{x509.DSAWithSHA256, oidSignatureDSAWithSHA256, x509.DSA, crypto.SHA256}, | ||
{x509.ECDSAWithSHA1, oidSignatureECDSAWithSHA1, x509.ECDSA, crypto.SHA1}, | ||
{x509.ECDSAWithSHA256, oidSignatureECDSAWithSHA256, x509.ECDSA, crypto.SHA256}, | ||
{x509.ECDSAWithSHA384, oidSignatureECDSAWithSHA384, x509.ECDSA, crypto.SHA384}, | ||
{x509.ECDSAWithSHA512, oidSignatureECDSAWithSHA512, x509.ECDSA, crypto.SHA512}, | ||
} | ||
|
||
var ( | ||
oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2} | ||
oidSignatureMD5WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4} | ||
oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} | ||
oidSignatureSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} | ||
oidSignatureSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} | ||
oidSignatureSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} | ||
oidSignatureRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} | ||
oidSignatureDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3} | ||
oidSignatureDSAWithSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2} | ||
oidSignatureECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} | ||
oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} | ||
oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} | ||
oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} | ||
|
||
oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} | ||
oidSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} | ||
oidSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3} | ||
|
||
oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8} | ||
|
||
// oidISOSignatureSHA1WithRSA means the same as oidSignatureSHA1WithRSA | ||
// but it's specified by ISO. Microsoft's makecert.exe has been known | ||
// to produce certificates with this OID. | ||
oidISOSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 29} | ||
|
||
oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7} | ||
) | ||
|
||
// added to Go in 1.9 | ||
var asn1NullRawValue = asn1.RawValue{ | ||
Tag: 5, /* ASN.1 NULL */ | ||
} |
Oops, something went wrong.