reimplement some stuff
This commit is contained in:
Родитель
cd12b49f8c
Коммит
c79ca75f72
|
@ -0,0 +1,58 @@
|
|||
package pkcs7
|
||||
|
||||
import (
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// anySet is a helper for dealing with SET OF ANY types.
|
||||
type anySet struct {
|
||||
Elements []asn1.RawValue `asn1:"set"`
|
||||
}
|
||||
|
||||
// decodeAnySet manually decodes a SET OF ANY type, since Go's parser can't
|
||||
// handle them.
|
||||
func decodeAnySet(rv asn1.RawValue) (as anySet, err error) {
|
||||
// Make sure it's really a SET.
|
||||
if rv.Class != asn1.ClassUniversal {
|
||||
err = fmt.Errorf("Bad class. Expecting %d, got %d", asn1.ClassUniversal, rv.Class)
|
||||
return
|
||||
}
|
||||
if rv.Tag != asn1.TagSet {
|
||||
err = fmt.Errorf("Bad tag. Expecting %d, got %d", asn1.TagSet, rv.Tag)
|
||||
return
|
||||
}
|
||||
|
||||
// Decode each element.
|
||||
der := rv.Bytes
|
||||
for len(der) > 0 {
|
||||
if der, err = asn1.Unmarshal(der, &rv); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
as.Elements = append(as.Elements, rv)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// encode manually encodes a SET OF ANY type, since Go's parser can't handle
|
||||
// them.
|
||||
func (as anySet) encode(dst *asn1.RawValue) (err error) {
|
||||
dst.Class = asn1.ClassUniversal
|
||||
dst.Tag = asn1.TagSet
|
||||
dst.IsCompound = true
|
||||
|
||||
var der []byte
|
||||
for _, elt := range as.Elements {
|
||||
if der, err = asn1.Marshal(elt); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dst.Bytes = append(dst.Bytes, der...)
|
||||
}
|
||||
|
||||
dst.FullBytes, err = asn1.Marshal(*dst)
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package pkcs7
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/asn1"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAnySet(t *testing.T) {
|
||||
// OpenSSL::ASN1::Set.new([
|
||||
// OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(5)),
|
||||
// OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(6))
|
||||
// ])
|
||||
der := []byte{49, 6, 2, 1, 5, 2, 1, 6}
|
||||
|
||||
var rv asn1.RawValue
|
||||
if rest, err := asn1.Unmarshal(der, &rv); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(rest) > 0 {
|
||||
t.Fatal("trailing data")
|
||||
}
|
||||
|
||||
as, err := decodeAnySet(rv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(as.Elements) != 2 {
|
||||
t.Fatal("bad decoded values")
|
||||
}
|
||||
|
||||
var i int
|
||||
if rest, err := asn1.Unmarshal(as.Elements[0].FullBytes, &i); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(rest) > 0 {
|
||||
t.Fatal("trailing data")
|
||||
}
|
||||
if i != 5 {
|
||||
t.Fatalf("bad decoded value: %d", i)
|
||||
}
|
||||
|
||||
if rest, err := asn1.Unmarshal(as.Elements[1].FullBytes, &i); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(rest) > 0 {
|
||||
t.Fatal("trailing data")
|
||||
}
|
||||
if i != 6 {
|
||||
t.Fatalf("bad decoded value: %d", i)
|
||||
}
|
||||
|
||||
var rv2 asn1.RawValue
|
||||
if err := as.encode(&rv2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(rv.FullBytes, rv2.FullBytes) {
|
||||
t.Fatal(hex.EncodeToString(rv2.FullBytes), " != ", hex.EncodeToString(rv.FullBytes))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,328 @@
|
|||
CryptographicMessageSyntax2004
|
||||
{ iso(1) member-body(2) us(840) rsadsi(113549)
|
||||
pkcs(1) pkcs-9(9) smime(16) modules(0) cms-2004(24) }
|
||||
|
||||
DEFINITIONS IMPLICIT TAGS ::=
|
||||
BEGIN
|
||||
|
||||
-- EXPORTS All
|
||||
-- The types and values defined in this module are exported for use
|
||||
-- in the other ASN.1 modules. Other applications may use them for
|
||||
-- their own purposes.
|
||||
|
||||
IMPORTS
|
||||
|
||||
-- Imports from RFC 5280 [PROFILE], Appendix A.1
|
||||
AlgorithmIdentifier, Certificate, CertificateList,
|
||||
CertificateSerialNumber, Name
|
||||
FROM PKIX1Explicit88
|
||||
{ iso(1) identified-organization(3) dod(6)
|
||||
internet(1) security(5) mechanisms(5) pkix(7)
|
||||
mod(0) pkix1-explicit(18) }
|
||||
|
||||
-- Imports from RFC 3281 [ACPROFILE], Appendix B
|
||||
AttributeCertificate
|
||||
FROM PKIXAttributeCertificate
|
||||
{ iso(1) identified-organization(3) dod(6)
|
||||
internet(1) security(5) mechanisms(5) pkix(7)
|
||||
mod(0) attribute-cert(12) }
|
||||
|
||||
-- Imports from Appendix B of this document
|
||||
AttributeCertificateV1
|
||||
FROM AttributeCertificateVersion1
|
||||
{ iso(1) member-body(2) us(840) rsadsi(113549)
|
||||
pkcs(1) pkcs-9(9) smime(16) modules(0)
|
||||
v1AttrCert(15) } ;
|
||||
|
||||
-- Cryptographic Message Syntax
|
||||
|
||||
ContentInfo ::= SEQUENCE {
|
||||
contentType ContentType,
|
||||
content [0] EXPLICIT ANY DEFINED BY contentType }
|
||||
|
||||
ContentType ::= OBJECT IDENTIFIER
|
||||
|
||||
SignedData ::= SEQUENCE {
|
||||
version CMSVersion,
|
||||
digestAlgorithms DigestAlgorithmIdentifiers,
|
||||
encapContentInfo EncapsulatedContentInfo,
|
||||
certificates [0] IMPLICIT CertificateSet OPTIONAL,
|
||||
crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
|
||||
signerInfos SignerInfos }
|
||||
|
||||
DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
|
||||
|
||||
SignerInfos ::= SET OF SignerInfo
|
||||
|
||||
EncapsulatedContentInfo ::= SEQUENCE {
|
||||
eContentType ContentType,
|
||||
eContent [0] EXPLICIT OCTET STRING OPTIONAL }
|
||||
|
||||
SignerInfo ::= SEQUENCE {
|
||||
version CMSVersion,
|
||||
sid SignerIdentifier,
|
||||
digestAlgorithm DigestAlgorithmIdentifier,
|
||||
signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
|
||||
signatureAlgorithm SignatureAlgorithmIdentifier,
|
||||
signature SignatureValue,
|
||||
unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }
|
||||
|
||||
SignerIdentifier ::= CHOICE {
|
||||
issuerAndSerialNumber IssuerAndSerialNumber,
|
||||
subjectKeyIdentifier [0] SubjectKeyIdentifier }
|
||||
|
||||
SignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
|
||||
UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
|
||||
Attribute ::= SEQUENCE {
|
||||
attrType OBJECT IDENTIFIER,
|
||||
attrValues SET OF AttributeValue }
|
||||
|
||||
AttributeValue ::= ANY
|
||||
|
||||
SignatureValue ::= OCTET STRING
|
||||
|
||||
EnvelopedData ::= SEQUENCE {
|
||||
version CMSVersion,
|
||||
originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
|
||||
recipientInfos RecipientInfos,
|
||||
encryptedContentInfo EncryptedContentInfo,
|
||||
unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
|
||||
|
||||
OriginatorInfo ::= SEQUENCE {
|
||||
certs [0] IMPLICIT CertificateSet OPTIONAL,
|
||||
crls [1] IMPLICIT RevocationInfoChoices OPTIONAL }
|
||||
|
||||
RecipientInfos ::= SET SIZE (1..MAX) OF RecipientInfo
|
||||
|
||||
EncryptedContentInfo ::= SEQUENCE {
|
||||
contentType ContentType,
|
||||
contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
|
||||
encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL }
|
||||
|
||||
EncryptedContent ::= OCTET STRING
|
||||
|
||||
UnprotectedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
|
||||
RecipientInfo ::= CHOICE {
|
||||
ktri KeyTransRecipientInfo,
|
||||
kari [1] KeyAgreeRecipientInfo,
|
||||
kekri [2] KEKRecipientInfo,
|
||||
pwri [3] PasswordRecipientInfo,
|
||||
ori [4] OtherRecipientInfo }
|
||||
|
||||
EncryptedKey ::= OCTET STRING
|
||||
|
||||
KeyTransRecipientInfo ::= SEQUENCE {
|
||||
version CMSVersion, -- always set to 0 or 2
|
||||
rid RecipientIdentifier,
|
||||
keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
|
||||
encryptedKey EncryptedKey }
|
||||
|
||||
RecipientIdentifier ::= CHOICE {
|
||||
issuerAndSerialNumber IssuerAndSerialNumber,
|
||||
subjectKeyIdentifier [0] SubjectKeyIdentifier }
|
||||
|
||||
KeyAgreeRecipientInfo ::= SEQUENCE {
|
||||
version CMSVersion, -- always set to 3
|
||||
originator [0] EXPLICIT OriginatorIdentifierOrKey,
|
||||
ukm [1] EXPLICIT UserKeyingMaterial OPTIONAL,
|
||||
keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
|
||||
recipientEncryptedKeys RecipientEncryptedKeys }
|
||||
|
||||
OriginatorIdentifierOrKey ::= CHOICE {
|
||||
issuerAndSerialNumber IssuerAndSerialNumber,
|
||||
subjectKeyIdentifier [0] SubjectKeyIdentifier,
|
||||
originatorKey [1] OriginatorPublicKey }
|
||||
|
||||
OriginatorPublicKey ::= SEQUENCE {
|
||||
algorithm AlgorithmIdentifier,
|
||||
publicKey BIT STRING }
|
||||
|
||||
RecipientEncryptedKeys ::= SEQUENCE OF RecipientEncryptedKey
|
||||
|
||||
RecipientEncryptedKey ::= SEQUENCE {
|
||||
rid KeyAgreeRecipientIdentifier,
|
||||
encryptedKey EncryptedKey }
|
||||
|
||||
KeyAgreeRecipientIdentifier ::= CHOICE {
|
||||
issuerAndSerialNumber IssuerAndSerialNumber,
|
||||
rKeyId [0] IMPLICIT RecipientKeyIdentifier }
|
||||
|
||||
RecipientKeyIdentifier ::= SEQUENCE {
|
||||
subjectKeyIdentifier SubjectKeyIdentifier,
|
||||
date GeneralizedTime OPTIONAL,
|
||||
other OtherKeyAttribute OPTIONAL }
|
||||
|
||||
SubjectKeyIdentifier ::= OCTET STRING
|
||||
|
||||
KEKRecipientInfo ::= SEQUENCE {
|
||||
version CMSVersion, -- always set to 4
|
||||
kekid KEKIdentifier,
|
||||
keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
|
||||
encryptedKey EncryptedKey }
|
||||
|
||||
KEKIdentifier ::= SEQUENCE {
|
||||
keyIdentifier OCTET STRING,
|
||||
date GeneralizedTime OPTIONAL,
|
||||
other OtherKeyAttribute OPTIONAL }
|
||||
|
||||
PasswordRecipientInfo ::= SEQUENCE {
|
||||
version CMSVersion, -- always set to 0
|
||||
keyDerivationAlgorithm [0] KeyDerivationAlgorithmIdentifier
|
||||
OPTIONAL,
|
||||
keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
|
||||
encryptedKey EncryptedKey }
|
||||
|
||||
OtherRecipientInfo ::= SEQUENCE {
|
||||
oriType OBJECT IDENTIFIER,
|
||||
oriValue ANY DEFINED BY oriType }
|
||||
|
||||
DigestedData ::= SEQUENCE {
|
||||
version CMSVersion,
|
||||
digestAlgorithm DigestAlgorithmIdentifier,
|
||||
encapContentInfo EncapsulatedContentInfo,
|
||||
digest Digest }
|
||||
|
||||
Digest ::= OCTET STRING
|
||||
|
||||
EncryptedData ::= SEQUENCE {
|
||||
version CMSVersion,
|
||||
encryptedContentInfo EncryptedContentInfo,
|
||||
unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
|
||||
|
||||
AuthenticatedData ::= SEQUENCE {
|
||||
version CMSVersion,
|
||||
originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
|
||||
recipientInfos RecipientInfos,
|
||||
macAlgorithm MessageAuthenticationCodeAlgorithm,
|
||||
digestAlgorithm [1] DigestAlgorithmIdentifier OPTIONAL,
|
||||
encapContentInfo EncapsulatedContentInfo,
|
||||
authAttrs [2] IMPLICIT AuthAttributes OPTIONAL,
|
||||
mac MessageAuthenticationCode,
|
||||
unauthAttrs [3] IMPLICIT UnauthAttributes OPTIONAL }
|
||||
|
||||
AuthAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
|
||||
UnauthAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
|
||||
MessageAuthenticationCode ::= OCTET STRING
|
||||
|
||||
DigestAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
|
||||
SignatureAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
|
||||
KeyEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
|
||||
ContentEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
|
||||
MessageAuthenticationCodeAlgorithm ::= AlgorithmIdentifier
|
||||
|
||||
KeyDerivationAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
|
||||
RevocationInfoChoices ::= SET OF RevocationInfoChoice
|
||||
|
||||
RevocationInfoChoice ::= CHOICE {
|
||||
crl CertificateList,
|
||||
other [1] IMPLICIT OtherRevocationInfoFormat }
|
||||
|
||||
OtherRevocationInfoFormat ::= SEQUENCE {
|
||||
otherRevInfoFormat OBJECT IDENTIFIER,
|
||||
otherRevInfo ANY DEFINED BY otherRevInfoFormat }
|
||||
|
||||
CertificateChoices ::= CHOICE {
|
||||
certificate Certificate,
|
||||
extendedCertificate [0] IMPLICIT ExtendedCertificate, -- Obsolete
|
||||
v1AttrCert [1] IMPLICIT AttributeCertificateV1, -- Obsolete
|
||||
v2AttrCert [2] IMPLICIT AttributeCertificateV2,
|
||||
other [3] IMPLICIT OtherCertificateFormat }
|
||||
|
||||
AttributeCertificateV2 ::= AttributeCertificate
|
||||
|
||||
OtherCertificateFormat ::= SEQUENCE {
|
||||
otherCertFormat OBJECT IDENTIFIER,
|
||||
otherCert ANY DEFINED BY otherCertFormat }
|
||||
|
||||
CertificateSet ::= SET OF CertificateChoices
|
||||
|
||||
IssuerAndSerialNumber ::= SEQUENCE {
|
||||
issuer Name,
|
||||
serialNumber CertificateSerialNumber }
|
||||
|
||||
CMSVersion ::= INTEGER { v0(0), v1(1), v2(2), v3(3), v4(4), v5(5) }
|
||||
|
||||
UserKeyingMaterial ::= OCTET STRING
|
||||
|
||||
OtherKeyAttribute ::= SEQUENCE {
|
||||
keyAttrId OBJECT IDENTIFIER,
|
||||
keyAttr ANY DEFINED BY keyAttrId OPTIONAL }
|
||||
|
||||
-- Content Type Object Identifiers
|
||||
|
||||
id-ct-contentInfo OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
||||
us(840) rsadsi(113549) pkcs(1) pkcs9(9) smime(16) ct(1) 6 }
|
||||
|
||||
id-data OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
||||
us(840) rsadsi(113549) pkcs(1) pkcs7(7) 1 }
|
||||
|
||||
id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
||||
us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 }
|
||||
|
||||
id-envelopedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
||||
us(840) rsadsi(113549) pkcs(1) pkcs7(7) 3 }
|
||||
|
||||
id-digestedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
||||
us(840) rsadsi(113549) pkcs(1) pkcs7(7) 5 }
|
||||
|
||||
id-encryptedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
||||
us(840) rsadsi(113549) pkcs(1) pkcs7(7) 6 }
|
||||
|
||||
id-ct-authData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
||||
us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) ct(1) 2 }
|
||||
|
||||
-- The CMS Attributes
|
||||
|
||||
MessageDigest ::= OCTET STRING
|
||||
|
||||
SigningTime ::= Time
|
||||
|
||||
Time ::= CHOICE {
|
||||
utcTime UTCTime,
|
||||
generalTime GeneralizedTime }
|
||||
|
||||
Countersignature ::= SignerInfo
|
||||
|
||||
-- Attribute Object Identifiers
|
||||
|
||||
id-contentType OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
||||
us(840) rsadsi(113549) pkcs(1) pkcs9(9) 3 }
|
||||
|
||||
id-messageDigest OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
||||
us(840) rsadsi(113549) pkcs(1) pkcs9(9) 4 }
|
||||
|
||||
id-signingTime OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
||||
us(840) rsadsi(113549) pkcs(1) pkcs9(9) 5 }
|
||||
|
||||
id-countersignature OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
||||
us(840) rsadsi(113549) pkcs(1) pkcs9(9) 6 }
|
||||
|
||||
-- Obsolete Extended Certificate syntax from PKCS #6
|
||||
|
||||
ExtendedCertificateOrCertificate ::= CHOICE {
|
||||
certificate Certificate,
|
||||
extendedCertificate [0] IMPLICIT ExtendedCertificate }
|
||||
|
||||
ExtendedCertificate ::= SEQUENCE {
|
||||
extendedCertificateInfo ExtendedCertificateInfo,
|
||||
signatureAlgorithm SignatureAlgorithmIdentifier,
|
||||
signature Signature }
|
||||
|
||||
ExtendedCertificateInfo ::= SEQUENCE {
|
||||
version CMSVersion,
|
||||
certificate Certificate,
|
||||
attributes UnauthAttributes }
|
||||
|
||||
Signature ::= BIT STRING
|
||||
|
||||
END -- of CryptographicMessageSyntax2004
|
778
pkcs7.go
778
pkcs7.go
|
@ -2,539 +2,373 @@
|
|||
package pkcs7
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
_ "crypto/sha1" // for crypto.SHA1
|
||||
)
|
||||
|
||||
// PKCS7 Represents a PKCS7 structure
|
||||
type PKCS7 struct {
|
||||
Content []byte
|
||||
Certificates []*x509.Certificate
|
||||
CRLs []pkix.CertificateList
|
||||
Signers []signerInfo
|
||||
raw interface{}
|
||||
}
|
||||
var (
|
||||
// ErrUnsupportedContentType is returned when a PKCS7 content is not supported.
|
||||
// Currently only Data (1.2.840.113549.1.7.1), Signed Data (1.2.840.113549.1.7.2),
|
||||
// and Enveloped Data are supported (1.2.840.113549.1.7.3)
|
||||
ErrUnsupportedContentType = errors.New("pkcs7: cannot parse data: unimplemented content type")
|
||||
|
||||
type contentInfo struct {
|
||||
ContentType asn1.ObjectIdentifier
|
||||
Content asn1.RawValue `asn1:"explicit,optional,tag:0"`
|
||||
}
|
||||
|
||||
// ErrUnsupportedContentType is returned when a PKCS7 content is not supported.
|
||||
// Currently only Data (1.2.840.113549.1.7.1), Signed Data (1.2.840.113549.1.7.2),
|
||||
// and Enveloped Data are supported (1.2.840.113549.1.7.3)
|
||||
var ErrUnsupportedContentType = errors.New("pkcs7: cannot parse data: unimplemented content type")
|
||||
|
||||
type unsignedData []byte
|
||||
// ErrWrongType is returned by methods that make assumptions about types.
|
||||
// Helper methods are defined for accessing CHOICE and ANY feilds. These
|
||||
// helper methods get the value of the field, assuming it is of a given type.
|
||||
// This error is returned if that assumption is wrong and the field has a
|
||||
// different type.
|
||||
ErrWrongType = errors.New("pkcs7: wrong choice or any type")
|
||||
)
|
||||
|
||||
var (
|
||||
oidData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1}
|
||||
oidSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}
|
||||
// Content type OIDs
|
||||
oidData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1}
|
||||
oidSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}
|
||||
|
||||
// Attribute OIDs
|
||||
oidAttributeContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3}
|
||||
oidAttributeMessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4}
|
||||
oidAttributeSigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5}
|
||||
)
|
||||
|
||||
type signedData struct {
|
||||
Version int `asn1:"default:1"`
|
||||
DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"`
|
||||
ContentInfo contentInfo
|
||||
Certificates rawCertificates `asn1:"optional,tag:0"`
|
||||
CRLs []pkix.CertificateList `asn1:"optional,tag:1"`
|
||||
SignerInfos []signerInfo `asn1:"set"`
|
||||
// ContentInfo ::= SEQUENCE {
|
||||
// contentType ContentType,
|
||||
// content [0] EXPLICIT ANY DEFINED BY contentType }
|
||||
//
|
||||
// ContentType ::= OBJECT IDENTIFIER
|
||||
type contentInfo struct {
|
||||
ContentType asn1.ObjectIdentifier
|
||||
Content asn1.RawValue `asn1:"explicit,tag:0"`
|
||||
}
|
||||
|
||||
type rawCertificates struct {
|
||||
Raw asn1.RawContent
|
||||
}
|
||||
|
||||
type attribute struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
Value asn1.RawValue `asn1:"set"`
|
||||
}
|
||||
|
||||
type issuerAndSerial struct {
|
||||
IssuerName asn1.RawValue
|
||||
SerialNumber *big.Int
|
||||
}
|
||||
|
||||
// MessageDigestMismatchError is returned when the signer data digest does not
|
||||
// match the computed digest for the contained content
|
||||
type MessageDigestMismatchError struct {
|
||||
ExpectedDigest []byte
|
||||
ActualDigest []byte
|
||||
}
|
||||
|
||||
func (err *MessageDigestMismatchError) Error() string {
|
||||
return fmt.Sprintf("pkcs7: Message digest mismatch\n\tExpected: %X\n\tActual : %X", err.ExpectedDigest, err.ActualDigest)
|
||||
}
|
||||
|
||||
type signerInfo struct {
|
||||
Version int `asn1:"default:1"`
|
||||
IssuerAndSerialNumber issuerAndSerial
|
||||
DigestAlgorithm pkix.AlgorithmIdentifier
|
||||
AuthenticatedAttributes []attribute `asn1:"optional,tag:0"`
|
||||
DigestEncryptionAlgorithm pkix.AlgorithmIdentifier
|
||||
EncryptedDigest []byte
|
||||
UnauthenticatedAttributes []attribute `asn1:"optional,tag:1"`
|
||||
}
|
||||
|
||||
// Parse decodes a DER encoded PKCS7 package
|
||||
func Parse(data []byte) (p7 *PKCS7, err error) {
|
||||
if len(data) == 0 {
|
||||
return nil, errors.New("pkcs7: input data is empty")
|
||||
// signedDataContent gets the content assuming contentType is signedData.
|
||||
func (ci contentInfo) signedDataContent() (sd signedData, err error) {
|
||||
if !ci.ContentType.Equal(oidSignedData) {
|
||||
err = ErrWrongType
|
||||
return
|
||||
}
|
||||
|
||||
var info contentInfo
|
||||
der, err := ber2der(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rest, err := asn1.Unmarshal(der, &info)
|
||||
if err != nil {
|
||||
var rest []byte
|
||||
if rest, err = asn1.Unmarshal(ci.Content.Bytes, &sd); err != nil {
|
||||
return
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
err = asn1.SyntaxError{Msg: "trailing data"}
|
||||
err = errors.New("unexpected trailing data")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// EncapsulatedContentInfo ::= SEQUENCE {
|
||||
// eContentType ContentType,
|
||||
// eContent [0] EXPLICIT OCTET STRING OPTIONAL }
|
||||
//
|
||||
// ContentType ::= OBJECT IDENTIFIER
|
||||
type encapsulatedContentInfo struct {
|
||||
EContentType asn1.ObjectIdentifier
|
||||
EContent asn1.RawValue `asn1:"optional,explicit,tag:0"`
|
||||
}
|
||||
|
||||
// dataEContent gets the EContent assuming EContentType is data. A nil byte
|
||||
// slice is returned if the OPTIONAL eContent field is missing.
|
||||
func (eci encapsulatedContentInfo) dataEContent() ([]byte, error) {
|
||||
if !eci.EContentType.Equal(oidData) {
|
||||
return nil, ErrWrongType
|
||||
}
|
||||
|
||||
return eci.EContent.Bytes, nil
|
||||
}
|
||||
|
||||
// Attribute ::= SEQUENCE {
|
||||
// attrType OBJECT IDENTIFIER,
|
||||
// attrValues SET OF AttributeValue }
|
||||
//
|
||||
// AttributeValue ::= ANY
|
||||
type attribute struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
|
||||
// This should be a SET OF ANY, but Go's asn1 parser can't handle slices of
|
||||
// RawValues. Use value() to get an AnySet of the value.
|
||||
RawValue asn1.RawValue
|
||||
}
|
||||
|
||||
// value further decodes the attribute value as a SET OF ANY, which Go's asn1
|
||||
// parser can't handle directly.
|
||||
func (a attribute) value() (anySet, error) {
|
||||
return decodeAnySet(a.RawValue)
|
||||
}
|
||||
|
||||
// SignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
//
|
||||
// UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
type attributes []attribute
|
||||
|
||||
// getOnlyAttributeValueBytes gets an attribute value, returning an error if the
|
||||
// attribute occurs multiple times or have multiple values.
|
||||
func (attrs attributes) getOnlyAttributeValueBytes(oid asn1.ObjectIdentifier) (rv asn1.RawValue, err error) {
|
||||
var vals []anySet
|
||||
if vals, err = attrs.getValues(oid); err != nil {
|
||||
return
|
||||
}
|
||||
if len(vals) != 1 {
|
||||
err = fmt.Errorf("expected 1 attribute found %d", len(vals))
|
||||
return
|
||||
}
|
||||
if len(vals[0].Elements) != 1 {
|
||||
err = fmt.Errorf("expected 1 attribute value found %d", len(vals[0].Elements))
|
||||
return
|
||||
}
|
||||
|
||||
if info.ContentType.Equal(oidSignedData) {
|
||||
return parseSignedData(info.Content.Bytes)
|
||||
} else {
|
||||
return nil, ErrUnsupportedContentType
|
||||
}
|
||||
return vals[0].Elements[0], nil
|
||||
}
|
||||
|
||||
func parseSignedData(data []byte) (*PKCS7, error) {
|
||||
var sd signedData
|
||||
asn1.Unmarshal(data, &sd)
|
||||
certs, err := sd.Certificates.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// fmt.Printf("--> Signed Data Version %d\n", sd.Version)
|
||||
|
||||
var compound asn1.RawValue
|
||||
var content unsignedData
|
||||
|
||||
// The Content.Bytes maybe empty on PKI responses.
|
||||
if len(sd.ContentInfo.Content.Bytes) > 0 {
|
||||
if _, err := asn1.Unmarshal(sd.ContentInfo.Content.Bytes, &compound); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Compound octet string
|
||||
if compound.IsCompound {
|
||||
if _, err = asn1.Unmarshal(compound.Bytes, &content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// assuming this is tag 04
|
||||
content = compound.Bytes
|
||||
}
|
||||
return &PKCS7{
|
||||
Content: content,
|
||||
Certificates: certs,
|
||||
CRLs: sd.CRLs,
|
||||
Signers: sd.SignerInfos,
|
||||
raw: sd}, nil
|
||||
}
|
||||
|
||||
func (raw rawCertificates) Parse() ([]*x509.Certificate, error) {
|
||||
if len(raw.Raw) == 0 {
|
||||
// get retreives the attributes with the given OID. A nil value is returned if
|
||||
// the OPTIONAL SET of Attributes is missing from the SignerInfo. An empty slice
|
||||
// is returned if the specified attribute isn't in the set.
|
||||
func (attrs attributes) getValues(oid asn1.ObjectIdentifier) ([]anySet, error) {
|
||||
if attrs == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var val asn1.RawValue
|
||||
if _, err := asn1.Unmarshal(raw.Raw, &val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return x509.ParseCertificates(val.Bytes)
|
||||
}
|
||||
|
||||
// Verify checks the signatures of a PKCS7 object
|
||||
// WARNING: Verify does not check signing time or verify certificate chains at
|
||||
// this time.
|
||||
func (p7 *PKCS7) Verify() (err error) {
|
||||
if len(p7.Signers) == 0 {
|
||||
return errors.New("pkcs7: Message has no signers")
|
||||
}
|
||||
for _, signer := range p7.Signers {
|
||||
if err := verifySignature(p7, signer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifySignature(p7 *PKCS7, signer signerInfo) error {
|
||||
signedData := p7.Content
|
||||
if len(signer.AuthenticatedAttributes) > 0 {
|
||||
// TODO(fullsailor): First check the content type match
|
||||
var digest []byte
|
||||
err := unmarshalAttribute(signer.AuthenticatedAttributes, oidAttributeMessageDigest, &digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h := hash.New()
|
||||
h.Write(p7.Content)
|
||||
computed := h.Sum(nil)
|
||||
if !hmac.Equal(digest, computed) {
|
||||
return &MessageDigestMismatchError{
|
||||
ExpectedDigest: digest,
|
||||
ActualDigest: computed,
|
||||
}
|
||||
}
|
||||
// TODO(fullsailor): Optionally verify certificate chain
|
||||
// TODO(fullsailor): Optionally verify signingTime against certificate NotAfter/NotBefore
|
||||
signedData, err = marshalAttributes(signer.AuthenticatedAttributes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cert := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)
|
||||
if cert == nil {
|
||||
return errors.New("pkcs7: No certificate for signer")
|
||||
}
|
||||
|
||||
algo := x509.SHA1WithRSA
|
||||
return cert.CheckSignature(algo, signedData, signer.EncryptedDigest)
|
||||
}
|
||||
|
||||
func marshalAttributes(attrs []attribute) ([]byte, error) {
|
||||
encodedAttributes, err := asn1.Marshal(struct {
|
||||
A []attribute `asn1:"set"`
|
||||
}{A: attrs})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remove the leading sequence octets
|
||||
var raw asn1.RawValue
|
||||
asn1.Unmarshal(encodedAttributes, &raw)
|
||||
return raw.Bytes, nil
|
||||
}
|
||||
|
||||
var (
|
||||
oidDigestAlgorithmSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26}
|
||||
oidEncryptionAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
|
||||
)
|
||||
|
||||
func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndSerial) *x509.Certificate {
|
||||
for _, cert := range certs {
|
||||
if isCertMatchForIssuerAndSerial(cert, ias) {
|
||||
return cert
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getHashForOID(oid asn1.ObjectIdentifier) (crypto.Hash, error) {
|
||||
switch {
|
||||
case oid.Equal(oidDigestAlgorithmSHA1):
|
||||
return crypto.SHA1, nil
|
||||
}
|
||||
return crypto.Hash(0), ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
// GetOnlySigner returns an x509.Certificate for the first signer of the signed
|
||||
// data payload. If there are more or less than one signer, nil is returned
|
||||
func (p7 *PKCS7) GetOnlySigner() *x509.Certificate {
|
||||
if len(p7.Signers) != 1 {
|
||||
return nil
|
||||
}
|
||||
signer := p7.Signers[0]
|
||||
return getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)
|
||||
}
|
||||
|
||||
// ErrUnsupportedAlgorithm tells you when our quick dev assumptions have failed
|
||||
var ErrUnsupportedAlgorithm = errors.New("pkcs7: cannot decrypt data: only RSA, DES, DES-EDE3, AES-256-CBC and AES-128-GCM supported")
|
||||
|
||||
func isCertMatchForIssuerAndSerial(cert *x509.Certificate, ias issuerAndSerial) bool {
|
||||
return cert.SerialNumber.Cmp(ias.SerialNumber) == 0 && bytes.Compare(cert.RawIssuer, ias.IssuerName.FullBytes) == 0
|
||||
}
|
||||
|
||||
func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, out interface{}) error {
|
||||
vals := []anySet{}
|
||||
for _, attr := range attrs {
|
||||
if attr.Type.Equal(attributeType) {
|
||||
_, err := asn1.Unmarshal(attr.Value.Bytes, out)
|
||||
return err
|
||||
if attr.Type.Equal(oid) {
|
||||
val, err := attr.value()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vals = append(vals, val)
|
||||
}
|
||||
}
|
||||
return errors.New("pkcs7: attribute type not in attributes")
|
||||
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// UnmarshalSignedAttribute decodes a single attribute from the signer info
|
||||
func (p7 *PKCS7) UnmarshalSignedAttribute(attributeType asn1.ObjectIdentifier, out interface{}) error {
|
||||
sd, ok := p7.raw.(signedData)
|
||||
if !ok {
|
||||
return errors.New("pkcs7: payload is not signedData content")
|
||||
// IssuerAndSerialNumber ::= SEQUENCE {
|
||||
// issuer Name,
|
||||
// serialNumber CertificateSerialNumber }
|
||||
//
|
||||
// CertificateSerialNumber ::= INTEGER
|
||||
type issuerAndSerialNumber struct {
|
||||
Issuer pkix.RDNSequence
|
||||
SerialNumber int
|
||||
}
|
||||
|
||||
// SignerInfo ::= SEQUENCE {
|
||||
// version CMSVersion,
|
||||
// sid SignerIdentifier,
|
||||
// digestAlgorithm DigestAlgorithmIdentifier,
|
||||
// signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
|
||||
// signatureAlgorithm SignatureAlgorithmIdentifier,
|
||||
// signature SignatureValue,
|
||||
// unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }
|
||||
//
|
||||
// CMSVersion ::= INTEGER
|
||||
// { v0(0), v1(1), v2(2), v3(3), v4(4), v5(5) }
|
||||
//
|
||||
// SignerIdentifier ::= CHOICE {
|
||||
// issuerAndSerialNumber IssuerAndSerialNumber,
|
||||
// subjectKeyIdentifier [0] SubjectKeyIdentifier }
|
||||
//
|
||||
// DigestAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
//
|
||||
// SignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
//
|
||||
// SignatureAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
//
|
||||
// SignatureValue ::= OCTET STRING
|
||||
//
|
||||
// UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
type signerInfo struct {
|
||||
Version int
|
||||
SID asn1.RawValue
|
||||
DigestAlgorithm pkix.AlgorithmIdentifier
|
||||
SignedAttrs attributes `asn1:"optional,tag:0"`
|
||||
SignatureAlgorithm pkix.AlgorithmIdentifier
|
||||
Signature []byte
|
||||
UnsignedAttrs attributes `asn1:"set,optional,tag:1"`
|
||||
}
|
||||
|
||||
// issuerAndSerialNumberSID gets the SID, assuming it is a issuerAndSerialNumber.
|
||||
func (si signerInfo) issuerAndSerialNumberSID() (isn issuerAndSerialNumber, err error) {
|
||||
if si.SID.Class != asn1.ClassUniversal || si.SID.Tag != asn1.TagSequence {
|
||||
err = ErrWrongType
|
||||
return
|
||||
}
|
||||
if len(sd.SignerInfos) < 1 {
|
||||
return errors.New("pkcs7: payload has no signers")
|
||||
|
||||
var rest []byte
|
||||
if rest, err = asn1.Unmarshal(si.SID.FullBytes, &isn); err == nil && len(rest) > 0 {
|
||||
err = errors.New("unexpected trailing data")
|
||||
}
|
||||
attributes := sd.SignerInfos[0].AuthenticatedAttributes
|
||||
return unmarshalAttribute(attributes, attributeType, out)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SignedData is an opaque data structure for creating signed data payloads
|
||||
type SignedData struct {
|
||||
sd signedData
|
||||
certs []*x509.Certificate
|
||||
messageDigest []byte
|
||||
// subjectKeyIdentifierSID gets the SID, assuming it is a subjectKeyIdentifier.
|
||||
func (si signerInfo) subjectKeyIdentifierSID() ([]byte, error) {
|
||||
if si.SID.Class != asn1.ClassContextSpecific || si.SID.Tag != 0 {
|
||||
return nil, ErrWrongType
|
||||
}
|
||||
|
||||
return si.SID.Bytes, nil
|
||||
}
|
||||
|
||||
// Attribute represents a key value pair attribute. Value must be marshalable byte
|
||||
// `encoding/asn1`
|
||||
type Attribute struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// SignerInfoConfig are optional values to include when adding a signer
|
||||
type SignerInfoConfig struct {
|
||||
ExtraSignedAttributes []Attribute
|
||||
}
|
||||
|
||||
// NewSignedData initializes a SignedData with content
|
||||
func NewSignedData(data []byte) (*SignedData, error) {
|
||||
content, err := asn1.Marshal(data)
|
||||
// getContentTypeAttribute gets the signed ContentType attribute from the
|
||||
// SignerInfo.
|
||||
func (si signerInfo) getContentTypeAttribute() (asn1.ObjectIdentifier, error) {
|
||||
rv, err := si.SignedAttrs.getOnlyAttributeValueBytes(oidAttributeContentType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ci := contentInfo{
|
||||
ContentType: oidData,
|
||||
Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true},
|
||||
|
||||
var ct asn1.ObjectIdentifier
|
||||
if rest, err := asn1.Unmarshal(rv.FullBytes, &ct); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) > 0 {
|
||||
return nil, errors.New("unexpected trailing data")
|
||||
}
|
||||
digAlg := pkix.AlgorithmIdentifier{
|
||||
Algorithm: oidDigestAlgorithmSHA1,
|
||||
|
||||
return ct, nil
|
||||
}
|
||||
|
||||
// getMessageDigestAttribute gets the signed MessageDigest attribute from the
|
||||
// SignerInfo.
|
||||
func (si signerInfo) getMessageDigestAttribute() ([]byte, error) {
|
||||
rv, err := si.SignedAttrs.getOnlyAttributeValueBytes(oidAttributeMessageDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := crypto.SHA1.New()
|
||||
h.Write(data)
|
||||
md := h.Sum(nil)
|
||||
sd := signedData{
|
||||
ContentInfo: ci,
|
||||
Version: 1,
|
||||
DigestAlgorithmIdentifiers: []pkix.AlgorithmIdentifier{digAlg},
|
||||
if rv.Class != asn1.ClassUniversal {
|
||||
return nil, fmt.Errorf("expected class %d, got %d", asn1.ClassUniversal, rv.Class)
|
||||
}
|
||||
return &SignedData{sd: sd, messageDigest: md}, nil
|
||||
}
|
||||
|
||||
type attributes struct {
|
||||
types []asn1.ObjectIdentifier
|
||||
values []interface{}
|
||||
}
|
||||
|
||||
// Add adds the attribute, maintaining insertion order
|
||||
func (attrs *attributes) Add(attrType asn1.ObjectIdentifier, value interface{}) {
|
||||
attrs.types = append(attrs.types, attrType)
|
||||
attrs.values = append(attrs.values, value)
|
||||
}
|
||||
|
||||
type sortableAttribute struct {
|
||||
SortKey []byte
|
||||
Attribute attribute
|
||||
}
|
||||
|
||||
type attributeSet []sortableAttribute
|
||||
|
||||
func (sa attributeSet) Len() int {
|
||||
return len(sa)
|
||||
}
|
||||
|
||||
func (sa attributeSet) Less(i, j int) bool {
|
||||
return bytes.Compare(sa[i].SortKey, sa[j].SortKey) < 0
|
||||
}
|
||||
|
||||
func (sa attributeSet) Swap(i, j int) {
|
||||
sa[i], sa[j] = sa[j], sa[i]
|
||||
}
|
||||
|
||||
func (sa attributeSet) Attributes() []attribute {
|
||||
attrs := make([]attribute, len(sa))
|
||||
for i, attr := range sa {
|
||||
attrs[i] = attr.Attribute
|
||||
if rv.Tag != asn1.TagOctetString {
|
||||
return nil, fmt.Errorf("expected tag %d, got %d", asn1.TagOctetString, rv.Tag)
|
||||
}
|
||||
return attrs
|
||||
|
||||
return rv.Bytes, nil
|
||||
}
|
||||
|
||||
func (attrs *attributes) ForMarshaling() ([]attribute, error) {
|
||||
sortables := make(attributeSet, len(attrs.types))
|
||||
for i := range sortables {
|
||||
attrType := attrs.types[i]
|
||||
attrValue := attrs.values[i]
|
||||
asn1Value, err := asn1.Marshal(attrValue)
|
||||
// getSigningTimeAttribute gets the signed SigningTime attribute from the
|
||||
// SignerInfo.
|
||||
func (si signerInfo) getSigningTimeAttribute() (time.Time, error) {
|
||||
var t time.Time
|
||||
|
||||
rv, err := si.SignedAttrs.getOnlyAttributeValueBytes(oidAttributeSigningTime)
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
if rv.Class != asn1.ClassUniversal {
|
||||
return t, fmt.Errorf("expected class %d, got %d", asn1.ClassUniversal, rv.Class)
|
||||
}
|
||||
if rv.Tag != asn1.TagUTCTime && rv.Tag != asn1.TagGeneralizedTime {
|
||||
return t, fmt.Errorf("expected tag %d or %d, got %d", asn1.TagUTCTime, asn1.TagGeneralizedTime, rv.Tag)
|
||||
}
|
||||
|
||||
if rest, err := asn1.Unmarshal(rv.FullBytes, &t); err != nil {
|
||||
return t, err
|
||||
} else if len(rest) > 0 {
|
||||
return t, errors.New("unexpected trailing data")
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// SignedData ::= SEQUENCE {
|
||||
// version CMSVersion,
|
||||
// digestAlgorithms DigestAlgorithmIdentifiers,
|
||||
// encapContentInfo EncapsulatedContentInfo,
|
||||
// certificates [0] IMPLICIT CertificateSet OPTIONAL,
|
||||
// crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
|
||||
// signerInfos SignerInfos }
|
||||
//
|
||||
// CMSVersion ::= INTEGER
|
||||
// { v0(0), v1(1), v2(2), v3(3), v4(4), v5(5) }
|
||||
//
|
||||
// DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
|
||||
//
|
||||
// CertificateSet ::= SET OF CertificateChoices
|
||||
//
|
||||
// CertificateChoices ::= CHOICE {
|
||||
// certificate Certificate,
|
||||
// extendedCertificate [0] IMPLICIT ExtendedCertificate, -- Obsolete
|
||||
// v1AttrCert [1] IMPLICIT AttributeCertificateV1, -- Obsolete
|
||||
// v2AttrCert [2] IMPLICIT AttributeCertificateV2,
|
||||
// other [3] IMPLICIT OtherCertificateFormat }
|
||||
//
|
||||
// OtherCertificateFormat ::= SEQUENCE {
|
||||
// otherCertFormat OBJECT IDENTIFIER,
|
||||
// otherCert ANY DEFINED BY otherCertFormat }
|
||||
//
|
||||
// RevocationInfoChoices ::= SET OF RevocationInfoChoice
|
||||
//
|
||||
// RevocationInfoChoice ::= CHOICE {
|
||||
// crl CertificateList,
|
||||
// other [1] IMPLICIT OtherRevocationInfoFormat }
|
||||
//
|
||||
// OtherRevocationInfoFormat ::= SEQUENCE {
|
||||
// otherRevInfoFormat OBJECT IDENTIFIER,
|
||||
// otherRevInfo ANY DEFINED BY otherRevInfoFormat }
|
||||
//
|
||||
// SignerInfos ::= SET OF SignerInfo
|
||||
type signedData struct {
|
||||
Version int
|
||||
DigestAlgorithms []pkix.AlgorithmIdentifier `asn1:"set"`
|
||||
EncapContentInfo encapsulatedContentInfo
|
||||
Certificates []asn1.RawValue `asn1:"optional,set,tag:0"`
|
||||
CRLs []asn1.RawValue `asn1:"optional,set,tag:1"`
|
||||
SignerInfos []signerInfo `asn1:"set"`
|
||||
}
|
||||
|
||||
// x509Certificates gets the certificates, assuming that they're X.509 encoded.
|
||||
func (sd signedData) x509Certificates() ([]*x509.Certificate, error) {
|
||||
// Certificates field is optional. Handle missing value.
|
||||
if sd.Certificates == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Empty set
|
||||
if len(sd.Certificates) == 0 {
|
||||
return []*x509.Certificate{}, nil
|
||||
}
|
||||
|
||||
certs := make([]*x509.Certificate, 0, len(sd.Certificates))
|
||||
for _, raw := range sd.Certificates {
|
||||
if raw.Class != asn1.ClassUniversal || raw.Tag != asn1.TagSequence {
|
||||
return nil, fmt.Errorf("Unsupported certificate type (class %d, tag %d)", raw.Class, raw.Tag)
|
||||
}
|
||||
|
||||
x509, err := x509.ParseCertificate(raw.FullBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attr := attribute{
|
||||
Type: attrType,
|
||||
Value: asn1.RawValue{Tag: 17, IsCompound: true, Bytes: asn1Value}, // 17 == SET tag
|
||||
}
|
||||
encoded, err := asn1.Marshal(attr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sortables[i] = sortableAttribute{
|
||||
SortKey: encoded,
|
||||
Attribute: attr,
|
||||
}
|
||||
|
||||
certs = append(certs, x509)
|
||||
}
|
||||
sort.Sort(sortables)
|
||||
return sortables.Attributes(), nil
|
||||
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
// AddSigner signs attributes about the content and adds certificate to payload
|
||||
func (sd *SignedData) AddSigner(cert *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error {
|
||||
attrs := &attributes{}
|
||||
attrs.Add(oidAttributeContentType, sd.sd.ContentInfo.ContentType)
|
||||
attrs.Add(oidAttributeMessageDigest, sd.messageDigest)
|
||||
attrs.Add(oidAttributeSigningTime, time.Now())
|
||||
for _, attr := range config.ExtraSignedAttributes {
|
||||
attrs.Add(attr.Type, attr.Value)
|
||||
}
|
||||
finalAttrs, err := attrs.ForMarshaling()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signature, err := signAttributes(finalAttrs, pkey, crypto.SHA1)
|
||||
if err != nil {
|
||||
return err
|
||||
// ParseContentInfo parses a top-level CMS ContentInfo packet.
|
||||
func ParseContentInfo(ber []byte) (ci contentInfo, err error) {
|
||||
var der []byte
|
||||
if der, err = ber2der(ber); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ias, err := cert2issuerAndSerial(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
var rest []byte
|
||||
if rest, err = asn1.Unmarshal(der, &ci); err != nil {
|
||||
return
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
err = errors.New("unexpected trailing data")
|
||||
}
|
||||
|
||||
signer := signerInfo{
|
||||
AuthenticatedAttributes: finalAttrs,
|
||||
DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidDigestAlgorithmSHA1},
|
||||
DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidEncryptionAlgorithmRSA},
|
||||
IssuerAndSerialNumber: ias,
|
||||
EncryptedDigest: signature,
|
||||
Version: 1,
|
||||
}
|
||||
// create signature of signed attributes
|
||||
sd.certs = append(sd.certs, cert)
|
||||
sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddCertificate adds the certificate to the payload. Useful for parent certificates
|
||||
func (sd *SignedData) AddCertificate(cert *x509.Certificate) {
|
||||
sd.certs = append(sd.certs, cert)
|
||||
}
|
||||
|
||||
// Detach removes content from the signed data struct to make it a detached signature.
|
||||
// This must be called right before Finish()
|
||||
func (sd *SignedData) Detach() {
|
||||
sd.sd.ContentInfo = contentInfo{ContentType: oidSignedData}
|
||||
}
|
||||
|
||||
// Finish marshals the content and its signers
|
||||
func (sd *SignedData) Finish() ([]byte, error) {
|
||||
sd.sd.Certificates = marshalCertificates(sd.certs)
|
||||
inner, err := asn1.Marshal(sd.sd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outer := contentInfo{
|
||||
ContentType: oidSignedData,
|
||||
Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true},
|
||||
}
|
||||
return asn1.Marshal(outer)
|
||||
}
|
||||
|
||||
func cert2issuerAndSerial(cert *x509.Certificate) (issuerAndSerial, error) {
|
||||
var ias issuerAndSerial
|
||||
// The issuer RDNSequence has to match exactly the sequence in the certificate
|
||||
// We cannot use cert.Issuer.ToRDNSequence() here since it mangles the sequence
|
||||
ias.IssuerName = asn1.RawValue{FullBytes: cert.RawIssuer}
|
||||
ias.SerialNumber = cert.SerialNumber
|
||||
|
||||
return ias, nil
|
||||
}
|
||||
|
||||
// signs the DER encoded form of the attributes with the private key
|
||||
func signAttributes(attrs []attribute, pkey crypto.PrivateKey, hash crypto.Hash) ([]byte, error) {
|
||||
attrBytes, err := marshalAttributes(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := hash.New()
|
||||
h.Write(attrBytes)
|
||||
hashed := h.Sum(nil)
|
||||
switch priv := pkey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA1, hashed)
|
||||
}
|
||||
return nil, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
// concats and wraps the certificates in the RawValue structure
|
||||
func marshalCertificates(certs []*x509.Certificate) rawCertificates {
|
||||
var buf bytes.Buffer
|
||||
for _, cert := range certs {
|
||||
buf.Write(cert.Raw)
|
||||
}
|
||||
rawCerts, _ := marshalCertificateBytes(buf.Bytes())
|
||||
return rawCerts
|
||||
}
|
||||
|
||||
// Even though, the tag & length are stripped out during marshalling the
|
||||
// RawContent, we have to encode it into the RawContent. If its missing,
|
||||
// then `asn1.Marshal()` will strip out the certificate wrapper instead.
|
||||
func marshalCertificateBytes(certs []byte) (rawCertificates, error) {
|
||||
var val = asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true}
|
||||
b, err := asn1.Marshal(val)
|
||||
if err != nil {
|
||||
return rawCertificates{}, err
|
||||
}
|
||||
return rawCertificates{Raw: b}, nil
|
||||
}
|
||||
|
||||
// DegenerateCertificate creates a signed data structure containing only the
|
||||
// provided certificate or certificate chain.
|
||||
func DegenerateCertificate(cert []byte) ([]byte, error) {
|
||||
rawCert, err := marshalCertificateBytes(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
emptyContent := contentInfo{ContentType: oidData}
|
||||
sd := signedData{
|
||||
Version: 1,
|
||||
ContentInfo: emptyContent,
|
||||
Certificates: rawCert,
|
||||
CRLs: []pkix.CertificateList{},
|
||||
}
|
||||
content, err := asn1.Marshal(sd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signedContent := contentInfo{
|
||||
ContentType: oidSignedData,
|
||||
Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true},
|
||||
}
|
||||
return asn1.Marshal(signedContent)
|
||||
return
|
||||
}
|
||||
|
|
706
pkcs7_test.go
706
pkcs7_test.go
|
@ -2,608 +2,196 @@ package pkcs7
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestVerify(t *testing.T) {
|
||||
fixture := UnmarshalTestFixture(SignedTestFixture)
|
||||
p7, err := Parse(fixture.Input)
|
||||
if err != nil {
|
||||
t.Errorf("Parse encountered unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := p7.Verify(); err != nil {
|
||||
t.Errorf("Verify failed with error: %v", err)
|
||||
}
|
||||
expected := []byte("We the People")
|
||||
if bytes.Compare(p7.Content, expected) != 0 {
|
||||
t.Errorf("Signed content does not match.\n\tExpected:%s\n\tActual:%s", expected, p7.Content)
|
||||
|
||||
}
|
||||
func TestParseFixtureSignatureOne(t *testing.T) {
|
||||
ParseContentInfoHelper(t, fixtureSignatureOne)
|
||||
}
|
||||
|
||||
func TestVerifyEC2(t *testing.T) {
|
||||
fixture := UnmarshalTestFixture(EC2IdentityDocumentFixture)
|
||||
p7, err := Parse(fixture.Input)
|
||||
if err != nil {
|
||||
t.Errorf("Parse encountered unexpected error: %v", err)
|
||||
}
|
||||
p7.Certificates = []*x509.Certificate{fixture.Certificate}
|
||||
if err := p7.Verify(); err != nil {
|
||||
t.Errorf("Verify failed with error: %v", err)
|
||||
}
|
||||
func TestParseSignatureGPGSM(t *testing.T) {
|
||||
ParseContentInfoHelper(t, fixtureSignatureGPGSM)
|
||||
}
|
||||
|
||||
func TestVerifyAppStore(t *testing.T) {
|
||||
fixture := UnmarshalTestFixture(AppStoreRecieptFixture)
|
||||
p7, err := Parse(fixture.Input)
|
||||
if err != nil {
|
||||
t.Errorf("Parse encountered unexpected error: %v", err)
|
||||
}
|
||||
if err := p7.Verify(); err != nil {
|
||||
t.Errorf("Verify failed with error: %v", err)
|
||||
}
|
||||
func TestParseSignatureNoCertsGPGSM(t *testing.T) {
|
||||
ParseContentInfoHelper(t, fixtureSignatureNoCertsGPGSM)
|
||||
}
|
||||
|
||||
func TestDegenerateCertificate(t *testing.T) {
|
||||
cert, err := createTestCertificate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
deg, err := DegenerateCertificate(cert.Certificate.Raw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testOpenSSLParse(t, deg)
|
||||
pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: deg})
|
||||
}
|
||||
func ParseContentInfoHelper(t *testing.T, ber []byte) {
|
||||
t.Helper()
|
||||
|
||||
// writes the cert to a temporary file and tests that openssl can read it.
|
||||
func testOpenSSLParse(t *testing.T, certBytes []byte) {
|
||||
tmpCertFile, err := ioutil.TempFile("", "testCertificate")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tmpCertFile.Name()) // clean up
|
||||
|
||||
if _, err := tmpCertFile.Write(certBytes); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
opensslCMD := exec.Command("openssl", "pkcs7", "-inform", "der", "-in", tmpCertFile.Name())
|
||||
_, err = opensslCMD.Output()
|
||||
ci, err := ParseContentInfo(ber)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := tmpCertFile.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
cert, err := createTestCertificate()
|
||||
sd, err := ci.signedDataContent()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
content := []byte("Hello World")
|
||||
for _, testDetach := range []bool{false, true} {
|
||||
toBeSigned, err := NewSignedData(content)
|
||||
|
||||
if _, err = sd.x509Certificates(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !sd.EncapContentInfo.EContentType.Equal(oidData) {
|
||||
t.Fatalf("expected %s content, got %s", oidData.String(), sd.EncapContentInfo.EContentType.String())
|
||||
}
|
||||
|
||||
if _, err = sd.EncapContentInfo.dataEContent(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, si := range sd.SignerInfos {
|
||||
// Allow either CHOICE for SID.
|
||||
if _, err = si.issuerAndSerialNumberSID(); err != nil {
|
||||
if _, err = si.subjectKeyIdentifierSID(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = si.subjectKeyIdentifierSID(); err != nil {
|
||||
if _, err = si.issuerAndSerialNumberSID(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
ct, err := si.getContentTypeAttribute()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot initialize signed data: %s", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := toBeSigned.AddSigner(cert.Certificate, cert.PrivateKey, SignerInfoConfig{}); err != nil {
|
||||
t.Fatalf("Cannot add signer: %s", err)
|
||||
// signerInfo contentType attribute must match signedData
|
||||
// encapsulatedContentInfo content type.
|
||||
if !ct.Equal(sd.EncapContentInfo.EContentType) {
|
||||
t.Fatalf("expected %s content, got %s", sd.EncapContentInfo.EContentType.String(), ct.String())
|
||||
}
|
||||
if testDetach {
|
||||
t.Log("Testing detached signature")
|
||||
toBeSigned.Detach()
|
||||
} else {
|
||||
t.Log("Testing attached signature")
|
||||
}
|
||||
signed, err := toBeSigned.Finish()
|
||||
|
||||
md, err := si.getMessageDigestAttribute() // TODO: test digest size equals hash size
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot finish signing data: %s", err)
|
||||
t.Fatal(err)
|
||||
} else if len(md) == 0 {
|
||||
t.Fatal("nil/empty message digest attribute")
|
||||
}
|
||||
pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed})
|
||||
p7, err := Parse(signed)
|
||||
|
||||
var nilTime time.Time
|
||||
st, err := si.getSigningTimeAttribute()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot parse our signed data: %s", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if testDetach {
|
||||
p7.Content = content
|
||||
}
|
||||
if bytes.Compare(content, p7.Content) != 0 {
|
||||
t.Errorf("Our content was not in the parsed data:\n\tExpected: %s\n\tActual: %s", content, p7.Content)
|
||||
}
|
||||
if err := p7.Verify(); err != nil {
|
||||
t.Errorf("Cannot verify our signed data: %s", err)
|
||||
if st == nilTime {
|
||||
t.Fatal("0 value signing time")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSignedData() {
|
||||
// generate a signing cert or load a key pair
|
||||
cert, err := createTestCertificate()
|
||||
if err != nil {
|
||||
fmt.Printf("Cannot create test certificates: %s", err)
|
||||
}
|
||||
|
||||
// Initialize a SignedData struct with content to be signed
|
||||
signedData, err := NewSignedData([]byte("Example data to be signed"))
|
||||
if err != nil {
|
||||
fmt.Printf("Cannot initialize signed data: %s", err)
|
||||
}
|
||||
|
||||
// Add the signing cert and private key
|
||||
if err := signedData.AddSigner(cert.Certificate, cert.PrivateKey, SignerInfoConfig{}); err != nil {
|
||||
fmt.Printf("Cannot add signer: %s", err)
|
||||
}
|
||||
|
||||
// Call Detach() is you want to remove content from the signature
|
||||
// and generate an S/MIME detached signature
|
||||
signedData.Detach()
|
||||
|
||||
// Finish() to obtain the signature bytes
|
||||
detachedSignature, err := signedData.Finish()
|
||||
if err != nil {
|
||||
fmt.Printf("Cannot finish signing data: %s", err)
|
||||
}
|
||||
pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: detachedSignature})
|
||||
}
|
||||
|
||||
func TestOpenSSLVerifyDetachedSignature(t *testing.T) {
|
||||
rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot generate root cert: %s", err)
|
||||
}
|
||||
signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", rootCert)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot generate signer cert: %s", err)
|
||||
}
|
||||
content := []byte("Hello World")
|
||||
toBeSigned, err := NewSignedData(content)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot initialize signed data: %s", err)
|
||||
}
|
||||
if err := toBeSigned.AddSigner(signerCert.Certificate, signerCert.PrivateKey, SignerInfoConfig{}); err != nil {
|
||||
t.Fatalf("Cannot add signer: %s", err)
|
||||
}
|
||||
toBeSigned.Detach()
|
||||
signed, err := toBeSigned.Finish()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot finish signing data: %s", err)
|
||||
}
|
||||
|
||||
// write the root cert to a temp file
|
||||
tmpRootCertFile, err := ioutil.TempFile("", "pkcs7TestRootCA")
|
||||
// round trip contentInfo
|
||||
der, err := ber2der(ber)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tmpRootCertFile.Name()) // clean up
|
||||
fd, err := os.OpenFile(tmpRootCertFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
|
||||
der2, err := asn1.Marshal(ci)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Certificate.Raw})
|
||||
fd.Close()
|
||||
|
||||
// write the signature to a temp file
|
||||
tmpSignatureFile, err := ioutil.TempFile("", "pkcs7Signature")
|
||||
if !bytes.Equal(der, der2) {
|
||||
t.Fatal("re-encoded contentInfo doesn't match original")
|
||||
}
|
||||
|
||||
// round trip signedData
|
||||
der = ci.Content.Bytes
|
||||
|
||||
der2, err = asn1.Marshal(sd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tmpSignatureFile.Name()) // clean up
|
||||
ioutil.WriteFile(tmpSignatureFile.Name(), signed, 0755)
|
||||
|
||||
// write the content to a temp file
|
||||
tmpContentFile, err := ioutil.TempFile("", "pkcs7Content")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tmpContentFile.Name()) // clean up
|
||||
ioutil.WriteFile(tmpContentFile.Name(), content, 0755)
|
||||
|
||||
// call openssl to verify the signature on the content using the root
|
||||
opensslCMD := exec.Command("openssl", "smime", "-verify",
|
||||
"-in", tmpSignatureFile.Name(), "-inform", "DER",
|
||||
"-content", tmpContentFile.Name(),
|
||||
"-CAfile", tmpRootCertFile.Name())
|
||||
out, err := opensslCMD.Output()
|
||||
t.Logf("%s", out)
|
||||
if err != nil {
|
||||
t.Fatalf("openssl command failed with %s", err)
|
||||
if !bytes.Equal(der, der2) {
|
||||
t.Fatal("re-encoded signedData doesn't match original")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalSignedAttribute(t *testing.T) {
|
||||
cert, err := createTestCertificate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
content := []byte("Hello World")
|
||||
toBeSigned, err := NewSignedData(content)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot initialize signed data: %s", err)
|
||||
}
|
||||
oidTest := asn1.ObjectIdentifier{2, 3, 4, 5, 6, 7}
|
||||
testValue := "TestValue"
|
||||
if err := toBeSigned.AddSigner(cert.Certificate, cert.PrivateKey, SignerInfoConfig{
|
||||
ExtraSignedAttributes: []Attribute{Attribute{Type: oidTest, Value: testValue}},
|
||||
}); err != nil {
|
||||
t.Fatalf("Cannot add signer: %s", err)
|
||||
}
|
||||
signed, err := toBeSigned.Finish()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot finish signing data: %s", err)
|
||||
}
|
||||
p7, err := Parse(signed)
|
||||
var actual string
|
||||
err = p7.UnmarshalSignedAttribute(oidTest, &actual)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot unmarshal test value: %s", err)
|
||||
}
|
||||
if testValue != actual {
|
||||
t.Errorf("Attribute does not match test value\n\tExpected: %s\n\tActual: %s", testValue, actual)
|
||||
var fixtureSignatureOne = mustBase64Decode("" +
|
||||
"MIIDVgYJKoZIhvcNAQcCoIIDRzCCA0MCAQExCTAHBgUrDgMCGjAcBgkqhkiG9w0B" +
|
||||
"BwGgDwQNV2UgdGhlIFBlb3BsZaCCAdkwggHVMIIBQKADAgECAgRpuDctMAsGCSqG" +
|
||||
"SIb3DQEBCzApMRAwDgYDVQQKEwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3Rh" +
|
||||
"cmswHhcNMTUwNTA2MDQyNDQ4WhcNMTYwNTA2MDQyNDQ4WjAlMRAwDgYDVQQKEwdB" +
|
||||
"Y21lIENvMREwDwYDVQQDEwhKb24gU25vdzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw" +
|
||||
"gYkCgYEAqr+tTF4mZP5rMwlXp1y+crRtFpuLXF1zvBZiYMfIvAHwo1ta8E1IcyEP" +
|
||||
"J1jIiKMcwbzeo6kAmZzIJRCTezq9jwXUsKbQTvcfOH9HmjUmXBRWFXZYoQs/OaaF" +
|
||||
"a45deHmwEeMQkuSWEtYiVKKZXtJOtflKIT3MryJEDiiItMkdybUCAwEAAaMSMBAw" +
|
||||
"DgYDVR0PAQH/BAQDAgCgMAsGCSqGSIb3DQEBCwOBgQDK1EweZWRL+f7Z+J0kVzY8" +
|
||||
"zXptcBaV4Lf5wGZJLJVUgp33bpLNpT3yadS++XQJ+cvtW3wADQzBSTMduyOF8Zf+" +
|
||||
"L7TjjrQ2+F2HbNbKUhBQKudxTfv9dJHdKbD+ngCCdQJYkIy2YexsoNG0C8nQkggy" +
|
||||
"axZd/J69xDVx6pui3Sj8sDGCATYwggEyAgEBMDEwKTEQMA4GA1UEChMHQWNtZSBD" +
|
||||
"bzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrAgRpuDctMAcGBSsOAwIaoGEwGAYJKoZI" +
|
||||
"hvcNAQkDMQsGCSqGSIb3DQEHATAgBgkqhkiG9w0BCQUxExcRMTUwNTA2MDAyNDQ4" +
|
||||
"LTA0MDAwIwYJKoZIhvcNAQkEMRYEFG9D7gcTh9zfKiYNJ1lgB0yTh4sZMAsGCSqG" +
|
||||
"SIb3DQEBAQSBgFF3sGDU9PtXty/QMtpcFa35vvIOqmWQAIZt93XAskQOnBq4OloX" +
|
||||
"iL9Ct7t1m4pzjRm0o9nDkbaSLZe7HKASHdCqijroScGlI8M+alJ8drHSFv6ZIjnM" +
|
||||
"FIwIf0B2Lko6nh9/6mUXq7tbbIHa3Gd1JUVire/QFFtmgRXMbXYk8SIS",
|
||||
)
|
||||
|
||||
var fixtureSignatureGPGSM = mustBase64Decode("" +
|
||||
"MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0B" +
|
||||
"BwEAAKCCA1gwggNUMIICPKADAgECAggWdNrn7G+uSDANBgkqhkiG9w0BAQsFADAU" +
|
||||
"MRIwEAYDVQQDEwlCZW4gVG9ld3MwIBcNMTcxMTE2MTc1MDMyWhgPMjA2MzA0MDUx" +
|
||||
"NzAwMDBaMBQxEjAQBgNVBAMTCUJlbiBUb2V3czCCASIwDQYJKoZIhvcNAQEBBQAD" +
|
||||
"ggEPADCCAQoCggEBAJ1x6MCSQ96Q8fpW4VsNxuR/lcOMIBhbclaVz63JWkFeg60N" +
|
||||
"0RMoVRCpfHGba6QtmVG7P/4C6C1qKNGaCvZug9jMvq+se49BWU2XfbtCQipqJrrs" +
|
||||
"cRabL/2m6KX53TmDODdjkfZWuQirOVmJ5PMhsUB+fJHUoDC8LnIXQ5rrQAfqtaDr" +
|
||||
"skCPHMTfuP9sECxSG821Y9tn6ZIesCBgTq1Bi2z1XnHzauU4/x3pXj0JFJDn0eAy" +
|
||||
"wkrs/qauGSnVMBsBTh8ghmjIoGLclmwIxMTHP7EAz6DZ1P5bkk+ib2LNt3MXhmPw" +
|
||||
"8f3md/YOW9/122W8BdWR49WjN7ICbE0yjmQlsAkCAwEAAaOBpzCBpDBuBgNVHREE" +
|
||||
"ZzBlgRRtYXN0YWh5ZXRpQGdtYWlsLmNvbYEVbWFzdGFoeWV0aUBnaXRodWIuY29t" +
|
||||
"gRFidG9ld3NAZ2l0aHViLmNvbYEjbWFzdGFoeWV0aUB1c2Vycy5ub3JlcGx5Lmdp" +
|
||||
"dGh1Yi5jb20wEQYKKwYBBAHaRwICAQQDAQH/MA8GA1UdEwEB/wQFMAMBAf8wDgYD" +
|
||||
"VR0PAQH/BAQDAgTwMA0GCSqGSIb3DQEBCwUAA4IBAQCK6sqkLqWEgQSyqSk3rnOp" +
|
||||
"SGdaKOB/qNqLVOUzn09iFg124aixygxmy+AjbY40tW4OC5jTNB0Y4jfVO6B2QK93" +
|
||||
"TvbwS7WvBlXMnHjPq8zQKMJvUWW4MyoZcm3s9JfP3ZMJ7JuaSSBCqJH16glQk+L0" +
|
||||
"L4uvCLho58WLC62+wbD0cH6fkQ5UKP60TSNuetpjfbeePYht+j9kY/NenBIkyKaZ" +
|
||||
"rRBr0gXkIoEaRtlY9EKL+Xm98MRyrLuh30qYhbtYoC5o2NblzRmC7hm59FimI3wg" +
|
||||
"SS+cBcflYAVZIqVCyW7nNWCpKgHtPym7bXU5NYF0OC4sa7PNpy0d8D8dOv13ks2v" +
|
||||
"MYIB4TCCAd0CAQEwIDAUMRIwEAYDVQQDEwlCZW4gVG9ld3MCCBZ02ufsb65IMA0G" +
|
||||
"CWCGSAFlAwQCAQUAoIGTMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZI" +
|
||||
"hvcNAQkFMQ8XDTE3MTExNzAwNDcyNFowKAYJKoZIhvcNAQkPMRswGTALBglghkgB" +
|
||||
"ZQMEAQIwCgYIKoZIhvcNAwcwLwYJKoZIhvcNAQkEMSIEIE3KD9X0JKMbA6uAfLrn" +
|
||||
"frMr8tCJ7tHO4VSzr+1FjeDcMA0GCSqGSIb3DQEBAQUABIIBAGH7rQRx3IPuJbPr" +
|
||||
"FjErvUWvgh8fS9s0mKI3/NPgUhx2gu1TpPdTp68La8KUDbN4jRVZ8o59WnzN9/So" +
|
||||
"5mpc0AcpVlolIb4B/qQMkBALx6O5nHE/lr7orXQWUPM3iSUHAscNZbNr98k8YBdl" +
|
||||
"hfarrderC+7n3dLOhNwpz3+STVr6l5czuXOqggcbwOMDbg4o/fiI2hm6eG79rDsd" +
|
||||
"MJ3NoMYnEURUtsK0OffSMpnbsifEyRviKQG0LC4neqMJGylm6uYOXfzNsCbP12MM" +
|
||||
"VovtxgUEskE2aU9UfPPqtm6H69QgcusUxxoECxWifydVObY/di5m5FGOCzP4b+QG" +
|
||||
"SX+du6QAAAAAAAA=",
|
||||
)
|
||||
|
||||
var fixtureSignatureNoCertsGPGSM = mustBase64Decode("" +
|
||||
"MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0B" +
|
||||
"BwEAADGCAeEwggHdAgEBMCAwFDESMBAGA1UEAxMJQmVuIFRvZXdzAggWdNrn7G+u" +
|
||||
"SDANBglghkgBZQMEAgEFAKCBkzAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwG" +
|
||||
"CSqGSIb3DQEJBTEPFw0xNzExMTcwMDQxNDhaMCgGCSqGSIb3DQEJDzEbMBkwCwYJ" +
|
||||
"YIZIAWUDBAECMAoGCCqGSIb3DQMHMC8GCSqGSIb3DQEJBDEiBCBNyg/V9CSjGwOr" +
|
||||
"gHy6536zK/LQie7RzuFUs6/tRY3g3DANBgkqhkiG9w0BAQEFAASCAQAvGAGPMaH3" +
|
||||
"oRiNDU0AGIVyjXUrZ8g2VRazGCTuuO0CPGWBDbBuuvCePuWTddcv5KHHyrYO0yUD" +
|
||||
"xergVhh1EXIsOItHbJ6QeMstmY8Ub7HGm4Srdtm3MMSEe24zRmKK5yvPfeaaXeb6" +
|
||||
"MASKXvViU/j9VDwUZ2CFPUzPq8DlS6j4w6dapfphFGN1wJV3ADLUzUkTXfXQ57HE" +
|
||||
"WUKdbxgcuyBH7eLhZpKAXP31iRKm2b7dV50SruRCqNYZOp8bUQ57bC2jels0dzQd" +
|
||||
"EQS76O/DH6eQ3/OgvpmR8BjlujA82tgjqP7fj0S7Cw2VlPqcey0iqRmAmiO2qzOI" +
|
||||
"KAYzMkxWr7iUAAAAAAAA",
|
||||
)
|
||||
|
||||
func mustBase64Decode(b64 string) []byte {
|
||||
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(b64))
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if _, err := io.Copy(buf, decoder); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
type certKeyPair struct {
|
||||
Certificate *x509.Certificate
|
||||
PrivateKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
func createTestCertificate() (certKeyPair, error) {
|
||||
signer, err := createTestCertificateByIssuer("Eddard Stark", nil)
|
||||
if err != nil {
|
||||
return certKeyPair{}, err
|
||||
}
|
||||
fmt.Println("Created root cert")
|
||||
pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Certificate.Raw})
|
||||
pair, err := createTestCertificateByIssuer("Jon Snow", signer)
|
||||
if err != nil {
|
||||
return certKeyPair{}, err
|
||||
}
|
||||
fmt.Println("Created signer cert")
|
||||
pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: pair.Certificate.Raw})
|
||||
return *pair, nil
|
||||
}
|
||||
|
||||
func createTestCertificateByIssuer(name string, issuer *certKeyPair) (*certKeyPair, error) {
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 32)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
SignatureAlgorithm: x509.SHA256WithRSA,
|
||||
Subject: pkix.Name{
|
||||
CommonName: name,
|
||||
Organization: []string{"Acme Co"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(1, 0, 0),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection},
|
||||
}
|
||||
var issuerCert *x509.Certificate
|
||||
var issuerKey crypto.PrivateKey
|
||||
if issuer != nil {
|
||||
issuerCert = issuer.Certificate
|
||||
issuerKey = issuer.PrivateKey
|
||||
} else {
|
||||
template.IsCA = true
|
||||
template.KeyUsage |= x509.KeyUsageCertSign
|
||||
issuerCert = &template
|
||||
issuerKey = priv
|
||||
}
|
||||
cert, err := x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.Public(), issuerKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
leaf, err := x509.ParseCertificate(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &certKeyPair{
|
||||
Certificate: leaf,
|
||||
PrivateKey: priv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type TestFixture struct {
|
||||
Input []byte
|
||||
Certificate *x509.Certificate
|
||||
PrivateKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
func UnmarshalTestFixture(testPEMBlock string) TestFixture {
|
||||
var result TestFixture
|
||||
var derBlock *pem.Block
|
||||
var pemBlock = []byte(testPEMBlock)
|
||||
for {
|
||||
derBlock, pemBlock = pem.Decode(pemBlock)
|
||||
if derBlock == nil {
|
||||
break
|
||||
}
|
||||
switch derBlock.Type {
|
||||
case "PKCS7":
|
||||
result.Input = derBlock.Bytes
|
||||
case "CERTIFICATE":
|
||||
result.Certificate, _ = x509.ParseCertificate(derBlock.Bytes)
|
||||
case "PRIVATE KEY":
|
||||
result.PrivateKey, _ = x509.ParsePKCS1PrivateKey(derBlock.Bytes)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func MarshalTestFixture(t TestFixture, w io.Writer) {
|
||||
if t.Input != nil {
|
||||
pem.Encode(w, &pem.Block{Type: "PKCS7", Bytes: t.Input})
|
||||
}
|
||||
if t.Certificate != nil {
|
||||
pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: t.Certificate.Raw})
|
||||
}
|
||||
if t.PrivateKey != nil {
|
||||
pem.Encode(w, &pem.Block{Type: "PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(t.PrivateKey)})
|
||||
}
|
||||
}
|
||||
|
||||
var SignedTestFixture = `
|
||||
-----BEGIN PKCS7-----
|
||||
MIIDVgYJKoZIhvcNAQcCoIIDRzCCA0MCAQExCTAHBgUrDgMCGjAcBgkqhkiG9w0B
|
||||
BwGgDwQNV2UgdGhlIFBlb3BsZaCCAdkwggHVMIIBQKADAgECAgRpuDctMAsGCSqG
|
||||
SIb3DQEBCzApMRAwDgYDVQQKEwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3Rh
|
||||
cmswHhcNMTUwNTA2MDQyNDQ4WhcNMTYwNTA2MDQyNDQ4WjAlMRAwDgYDVQQKEwdB
|
||||
Y21lIENvMREwDwYDVQQDEwhKb24gU25vdzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
|
||||
gYkCgYEAqr+tTF4mZP5rMwlXp1y+crRtFpuLXF1zvBZiYMfIvAHwo1ta8E1IcyEP
|
||||
J1jIiKMcwbzeo6kAmZzIJRCTezq9jwXUsKbQTvcfOH9HmjUmXBRWFXZYoQs/OaaF
|
||||
a45deHmwEeMQkuSWEtYiVKKZXtJOtflKIT3MryJEDiiItMkdybUCAwEAAaMSMBAw
|
||||
DgYDVR0PAQH/BAQDAgCgMAsGCSqGSIb3DQEBCwOBgQDK1EweZWRL+f7Z+J0kVzY8
|
||||
zXptcBaV4Lf5wGZJLJVUgp33bpLNpT3yadS++XQJ+cvtW3wADQzBSTMduyOF8Zf+
|
||||
L7TjjrQ2+F2HbNbKUhBQKudxTfv9dJHdKbD+ngCCdQJYkIy2YexsoNG0C8nQkggy
|
||||
axZd/J69xDVx6pui3Sj8sDGCATYwggEyAgEBMDEwKTEQMA4GA1UEChMHQWNtZSBD
|
||||
bzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrAgRpuDctMAcGBSsOAwIaoGEwGAYJKoZI
|
||||
hvcNAQkDMQsGCSqGSIb3DQEHATAgBgkqhkiG9w0BCQUxExcRMTUwNTA2MDAyNDQ4
|
||||
LTA0MDAwIwYJKoZIhvcNAQkEMRYEFG9D7gcTh9zfKiYNJ1lgB0yTh4sZMAsGCSqG
|
||||
SIb3DQEBAQSBgFF3sGDU9PtXty/QMtpcFa35vvIOqmWQAIZt93XAskQOnBq4OloX
|
||||
iL9Ct7t1m4pzjRm0o9nDkbaSLZe7HKASHdCqijroScGlI8M+alJ8drHSFv6ZIjnM
|
||||
FIwIf0B2Lko6nh9/6mUXq7tbbIHa3Gd1JUVire/QFFtmgRXMbXYk8SIS
|
||||
-----END PKCS7-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB1TCCAUCgAwIBAgIEabg3LTALBgkqhkiG9w0BAQswKTEQMA4GA1UEChMHQWNt
|
||||
ZSBDbzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrMB4XDTE1MDUwNjA0MjQ0OFoXDTE2
|
||||
MDUwNjA0MjQ0OFowJTEQMA4GA1UEChMHQWNtZSBDbzERMA8GA1UEAxMISm9uIFNu
|
||||
b3cwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKq/rUxeJmT+azMJV6dcvnK0
|
||||
bRabi1xdc7wWYmDHyLwB8KNbWvBNSHMhDydYyIijHMG83qOpAJmcyCUQk3s6vY8F
|
||||
1LCm0E73Hzh/R5o1JlwUVhV2WKELPzmmhWuOXXh5sBHjEJLklhLWIlSimV7STrX5
|
||||
SiE9zK8iRA4oiLTJHcm1AgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIAoDALBgkqhkiG
|
||||
9w0BAQsDgYEAytRMHmVkS/n+2fidJFc2PM16bXAWleC3+cBmSSyVVIKd926SzaU9
|
||||
8mnUvvl0CfnL7Vt8AA0MwUkzHbsjhfGX/i+04460Nvhdh2zWylIQUCrncU37/XSR
|
||||
3Smw/p4AgnUCWJCMtmHsbKDRtAvJ0JIIMmsWXfyevcQ1ceqbot0o/LA=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIICXgIBAAKBgQCqv61MXiZk/mszCVenXL5ytG0Wm4tcXXO8FmJgx8i8AfCjW1rw
|
||||
TUhzIQ8nWMiIoxzBvN6jqQCZnMglEJN7Or2PBdSwptBO9x84f0eaNSZcFFYVdlih
|
||||
Cz85poVrjl14ebAR4xCS5JYS1iJUople0k61+UohPcyvIkQOKIi0yR3JtQIDAQAB
|
||||
AoGBAIPLCR9N+IKxodq11lNXEaUFwMHXc1zqwP8no+2hpz3+nVfplqqubEJ4/PJY
|
||||
5AgbJoIfnxVhyBXJXu7E+aD/OPneKZrgp58YvHKgGvvPyJg2gpC/1Fh0vQB0HNpI
|
||||
1ZzIZUl8ZTUtVgtnCBUOh5JGI4bFokAqrT//Uvcfd+idgxqBAkEA1ZbP/Kseld14
|
||||
qbWmgmU5GCVxsZRxgR1j4lG3UVjH36KXMtRTm1atAam1uw3OEGa6Y3ANjpU52FaB
|
||||
Hep5rkk4FQJBAMynMo1L1uiN5GP+KYLEF5kKRxK+FLjXR0ywnMh+gpGcZDcOae+J
|
||||
+t1gLoWBIESH/Xt639T7smuSfrZSA9V0EyECQA8cvZiWDvLxmaEAXkipmtGPjKzQ
|
||||
4PsOtkuEFqFl07aKDYKmLUg3aMROWrJidqsIabWxbvQgsNgSvs38EiH3wkUCQQCg
|
||||
ndxb7piVXb9RBwm3OoU2tE1BlXMX+sVXmAkEhd2dwDsaxrI3sHf1xGXem5AimQRF
|
||||
JBOFyaCnMotGNioSHY5hAkEAxyXcNixQ2RpLXJTQZtwnbk0XDcbgB+fBgXnv/4f3
|
||||
BCvcu85DqJeJyQv44Oe1qsXEX9BfcQIOVaoep35RPlKi9g==
|
||||
-----END PRIVATE KEY-----`
|
||||
|
||||
// Content is "This is a test"
|
||||
var EncryptedTestFixture = `
|
||||
-----BEGIN PKCS7-----
|
||||
MIIBFwYJKoZIhvcNAQcDoIIBCDCCAQQCAQAxgcowgccCAQAwMjApMRAwDgYDVQQK
|
||||
EwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3RhcmsCBQDL+CvWMAsGCSqGSIb3
|
||||
DQEBAQSBgKyP/5WlRTZD3dWMrLOX6QRNDrXEkQjhmToRwFZdY3LgUh25ZU0S/q4G
|
||||
dHPV21Fv9lQD+q7l3vfeHw8M6Z1PKi9sHMVfxAkQpvaI96DTIT3YHtuLC1w3geCO
|
||||
8eFWTq2qS4WChSuS/yhYosjA1kTkE0eLnVZcGw0z/WVuEZznkdyIMDIGCSqGSIb3
|
||||
DQEHATARBgUrDgMCBwQImpKsUyMPpQigEgQQRcWWrCRXqpD5Njs0GkJl+g==
|
||||
-----END PKCS7-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB1jCCAUGgAwIBAgIFAMv4K9YwCwYJKoZIhvcNAQELMCkxEDAOBgNVBAoTB0Fj
|
||||
bWUgQ28xFTATBgNVBAMTDEVkZGFyZCBTdGFyazAeFw0xNTA1MDYwMzU2NDBaFw0x
|
||||
NjA1MDYwMzU2NDBaMCUxEDAOBgNVBAoTB0FjbWUgQ28xETAPBgNVBAMTCEpvbiBT
|
||||
bm93MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK6NU0R0eiCYVquU4RcjKc
|
||||
LzGfx0aa1lMr2TnLQUSeLFZHFxsyyMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg
|
||||
8+Zg2r8xnnney7abxcuv0uATWSIeKlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP
|
||||
+Zxp2ni5qHNraf3wE2VPIQIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCAKAwCwYJKoZI
|
||||
hvcNAQELA4GBAIr2F7wsqmEU/J/kLyrCgEVXgaV/sKZq4pPNnzS0tBYk8fkV3V18
|
||||
sBJyHKRLL/wFZASvzDcVGCplXyMdAOCyfd8jO3F9Ac/xdlz10RrHJT75hNu3a7/n
|
||||
9KNwKhfN4A1CQv2x372oGjRhCW5bHNCWx4PIVeNzCyq/KZhyY9sxHE6f
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIICXgIBAAKBgQDK6NU0R0eiCYVquU4RcjKcLzGfx0aa1lMr2TnLQUSeLFZHFxsy
|
||||
yMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg8+Zg2r8xnnney7abxcuv0uATWSIe
|
||||
KlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP+Zxp2ni5qHNraf3wE2VPIQIDAQAB
|
||||
AoGBALyvnSt7KUquDen7nXQtvJBudnf9KFPt//OjkdHHxNZNpoF/JCSqfQeoYkeu
|
||||
MdAVYNLQGMiRifzZz4dDhA9xfUAuy7lcGQcMCxEQ1dwwuFaYkawbS0Tvy2PFlq2d
|
||||
H5/HeDXU4EDJ3BZg0eYj2Bnkt1sJI35UKQSxblQ0MY2q0uFBAkEA5MMOogkgUx1C
|
||||
67S1tFqMUSM8D0mZB0O5vOJZC5Gtt2Urju6vywge2ArExWRXlM2qGl8afFy2SgSv
|
||||
Xk5eybcEiQJBAOMRwwbEoW5NYHuFFbSJyWll4n71CYuWuQOCzehDPyTb80WFZGLV
|
||||
i91kFIjeERyq88eDE5xVB3ZuRiXqaShO/9kCQQCKOEkpInaDgZSjskZvuJ47kByD
|
||||
6CYsO4GIXQMMeHML8ncFH7bb6AYq5ybJVb2NTU7QLFJmfeYuhvIm+xdOreRxAkEA
|
||||
o5FC5Jg2FUfFzZSDmyZ6IONUsdF/i78KDV5nRv1R+hI6/oRlWNCtTNBv/lvBBd6b
|
||||
dseUE9QoaQZsn5lpILEvmQJAZ0B+Or1rAYjnbjnUhdVZoy9kC4Zov+4UH3N/BtSy
|
||||
KJRWUR0wTWfZBPZ5hAYZjTBEAFULaYCXlQKsODSp0M1aQA==
|
||||
-----END PRIVATE KEY-----`
|
||||
|
||||
var EC2IdentityDocumentFixture = `
|
||||
-----BEGIN PKCS7-----
|
||||
MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAaCA
|
||||
JIAEggGmewogICJwcml2YXRlSXAiIDogIjE3Mi4zMC4wLjI1MiIsCiAgImRldnBh
|
||||
eVByb2R1Y3RDb2RlcyIgOiBudWxsLAogICJhdmFpbGFiaWxpdHlab25lIiA6ICJ1
|
||||
cy1lYXN0LTFhIiwKICAidmVyc2lvbiIgOiAiMjAxMC0wOC0zMSIsCiAgImluc3Rh
|
||||
bmNlSWQiIDogImktZjc5ZmU1NmMiLAogICJiaWxsaW5nUHJvZHVjdHMiIDogbnVs
|
||||
bCwKICAiaW5zdGFuY2VUeXBlIiA6ICJ0Mi5taWNybyIsCiAgImFjY291bnRJZCIg
|
||||
OiAiMTIxNjU5MDE0MzM0IiwKICAiaW1hZ2VJZCIgOiAiYW1pLWZjZTNjNjk2IiwK
|
||||
ICAicGVuZGluZ1RpbWUiIDogIjIwMTYtMDQtMDhUMDM6MDE6MzhaIiwKICAiYXJj
|
||||
aGl0ZWN0dXJlIiA6ICJ4ODZfNjQiLAogICJrZXJuZWxJZCIgOiBudWxsLAogICJy
|
||||
YW1kaXNrSWQiIDogbnVsbCwKICAicmVnaW9uIiA6ICJ1cy1lYXN0LTEiCn0AAAAA
|
||||
AAAxggEYMIIBFAIBATBpMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5n
|
||||
dG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2Vi
|
||||
IFNlcnZpY2VzIExMQwIJAJa6SNnlXhpnMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0B
|
||||
CQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNjA0MDgwMzAxNDRaMCMG
|
||||
CSqGSIb3DQEJBDEWBBTuUc28eBXmImAautC+wOjqcFCBVjAJBgcqhkjOOAQDBC8w
|
||||
LQIVAKA54NxGHWWCz5InboDmY/GHs33nAhQ6O/ZI86NwjA9Vz3RNMUJrUPU5tAAA
|
||||
AAAAAA==
|
||||
-----END PKCS7-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw
|
||||
FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD
|
||||
VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z
|
||||
ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u
|
||||
IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl
|
||||
cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e
|
||||
ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3
|
||||
VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P
|
||||
hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j
|
||||
k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U
|
||||
hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF
|
||||
lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf
|
||||
MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW
|
||||
MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw
|
||||
vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw
|
||||
7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
var AppStoreRecieptFixture = `
|
||||
-----BEGIN PKCS7-----
|
||||
MIITtgYJKoZIhvcNAQcCoIITpzCCE6MCAQExCzAJBgUrDgMCGgUAMIIDVwYJKoZI
|
||||
hvcNAQcBoIIDSASCA0QxggNAMAoCAQgCAQEEAhYAMAoCARQCAQEEAgwAMAsCAQEC
|
||||
AQEEAwIBADALAgEDAgEBBAMMATEwCwIBCwIBAQQDAgEAMAsCAQ8CAQEEAwIBADAL
|
||||
AgEQAgEBBAMCAQAwCwIBGQIBAQQDAgEDMAwCAQoCAQEEBBYCNCswDAIBDgIBAQQE
|
||||
AgIAjTANAgENAgEBBAUCAwFgvTANAgETAgEBBAUMAzEuMDAOAgEJAgEBBAYCBFAy
|
||||
NDcwGAIBAgIBAQQQDA5jb20uemhpaHUudGVzdDAYAgEEAgECBBCS+ZODNMHwT1Nz
|
||||
gWYDXyWZMBsCAQACAQEEEwwRUHJvZHVjdGlvblNhbmRib3gwHAIBBQIBAQQU4nRh
|
||||
YCEZx70Flzv7hvJRjJZckYIwHgIBDAIBAQQWFhQyMDE2LTA3LTIzVDA2OjIxOjEx
|
||||
WjAeAgESAgEBBBYWFDIwMTMtMDgtMDFUMDc6MDA6MDBaMD0CAQYCAQEENbR21I+a
|
||||
8+byMXo3NPRoDWQmSXQF2EcCeBoD4GaL//ZCRETp9rGFPSg1KekCP7Kr9HAqw09m
|
||||
MEICAQcCAQEEOlVJozYYBdugybShbiiMsejDMNeCbZq6CrzGBwW6GBy+DGWxJI91
|
||||
Y3ouXN4TZUhuVvLvN1b0m5T3ggQwggFaAgERAgEBBIIBUDGCAUwwCwICBqwCAQEE
|
||||
AhYAMAsCAgatAgEBBAIMADALAgIGsAIBAQQCFgAwCwICBrICAQEEAgwAMAsCAgaz
|
||||
AgEBBAIMADALAgIGtAIBAQQCDAAwCwICBrUCAQEEAgwAMAsCAga2AgEBBAIMADAM
|
||||
AgIGpQIBAQQDAgEBMAwCAgarAgEBBAMCAQEwDAICBq4CAQEEAwIBADAMAgIGrwIB
|
||||
AQQDAgEAMAwCAgaxAgEBBAMCAQAwGwICBqcCAQEEEgwQMTAwMDAwMDIyNTMyNTkw
|
||||
MTAbAgIGqQIBAQQSDBAxMDAwMDAwMjI1MzI1OTAxMB8CAgaoAgEBBBYWFDIwMTYt
|
||||
MDctMjNUMDY6MjE6MTFaMB8CAgaqAgEBBBYWFDIwMTYtMDctMjNUMDY6MjE6MTFa
|
||||
MCACAgamAgEBBBcMFWNvbS56aGlodS50ZXN0LnRlc3RfMaCCDmUwggV8MIIEZKAD
|
||||
AgECAggO61eH554JjTANBgkqhkiG9w0BAQUFADCBljELMAkGA1UEBhMCVVMxEzAR
|
||||
BgNVBAoMCkFwcGxlIEluYy4xLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZl
|
||||
bG9wZXIgUmVsYXRpb25zMUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxv
|
||||
cGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNTExMTMw
|
||||
MjE1MDlaFw0yMzAyMDcyMTQ4NDdaMIGJMTcwNQYDVQQDDC5NYWMgQXBwIFN0b3Jl
|
||||
IGFuZCBpVHVuZXMgU3RvcmUgUmVjZWlwdCBTaWduaW5nMSwwKgYDVQQLDCNBcHBs
|
||||
ZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczETMBEGA1UECgwKQXBwbGUg
|
||||
SW5jLjELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||
AQClz4H9JaKBW9aH7SPaMxyO4iPApcQmyz3Gn+xKDVWG/6QC15fKOVRtfX+yVBid
|
||||
xCxScY5ke4LOibpJ1gjltIhxzz9bRi7GxB24A6lYogQ+IXjV27fQjhKNg0xbKmg3
|
||||
k8LyvR7E0qEMSlhSqxLj7d0fmBWQNS3CzBLKjUiB91h4VGvojDE2H0oGDEdU8zeQ
|
||||
uLKSiX1fpIVK4cCc4Lqku4KXY/Qrk8H9Pm/KwfU8qY9SGsAlCnYO3v6Z/v/Ca/Vb
|
||||
XqxzUUkIVonMQ5DMjoEC0KCXtlyxoWlph5AQaCYmObgdEHOwCl3Fc9DfdjvYLdmI
|
||||
HuPsB8/ijtDT+iZVge/iA0kjAgMBAAGjggHXMIIB0zA/BggrBgEFBQcBAQQzMDEw
|
||||
LwYIKwYBBQUHMAGGI2h0dHA6Ly9vY3NwLmFwcGxlLmNvbS9vY3NwMDMtd3dkcjA0
|
||||
MB0GA1UdDgQWBBSRpJz8xHa3n6CK9E31jzZd7SsEhTAMBgNVHRMBAf8EAjAAMB8G
|
||||
A1UdIwQYMBaAFIgnFwmpthhgi+zruvZHWcVSVKO3MIIBHgYDVR0gBIIBFTCCAREw
|
||||
ggENBgoqhkiG92NkBQYBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9u
|
||||
IHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5j
|
||||
ZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25k
|
||||
aXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0
|
||||
aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipodHRwOi8vd3d3
|
||||
LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wDgYDVR0PAQH/BAQDAgeA
|
||||
MBAGCiqGSIb3Y2QGCwEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQANphvTLj3jWysH
|
||||
bkKWbNPojEMwgl/gXNGNvr0PvRr8JZLbjIXDgFnf4+LXLgUUrA3btrj+/DUufMut
|
||||
F2uOfx/kd7mxZ5W0E16mGYZ2+FogledjjA9z/Ojtxh+umfhlSFyg4Cg6wBA3Lbmg
|
||||
BDkfc7nIBf3y3n8aKipuKwH8oCBc2et9J6Yz+PWY4L5E27FMZ/xuCk/J4gao0pfz
|
||||
p45rUaJahHVl0RYEYuPBX/UIqc9o2ZIAycGMs/iNAGS6WGDAfK+PdcppuVsq1h1o
|
||||
bphC9UynNxmbzDscehlD86Ntv0hgBgw2kivs3hi1EdotI9CO/KBpnBcbnoB7OUdF
|
||||
MGEvxxOoMIIEIjCCAwqgAwIBAgIIAd68xDltoBAwDQYJKoZIhvcNAQEFBQAwYjEL
|
||||
MAkGA1UEBhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxl
|
||||
IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENB
|
||||
MB4XDTEzMDIwNzIxNDg0N1oXDTIzMDIwNzIxNDg0N1owgZYxCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUg
|
||||
RGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERl
|
||||
dmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0G
|
||||
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKOFSmy1aqyCQ5SOmM7uxfuH8mkbw0
|
||||
U3rOfGOAYXdkXqUHI7Y5/lAtFVZYcC1+xG7BSoU+L/DehBqhV8mvexj/avoVEkkV
|
||||
CBmsqtsqMu2WY2hSFT2Miuy/axiV4AOsAX2XBWfODoWVN2rtCbauZ81RZJ/GXNG8
|
||||
V25nNYB2NqSHgW44j9grFU57Jdhav06DwY3Sk9UacbVgnJ0zTlX5ElgMhrgWDcHl
|
||||
d0WNUEi6Ky3klIXh6MSdxmilsKP8Z35wugJZS3dCkTm59c3hTO/AO0iMpuUhXf1q
|
||||
arunFjVg0uat80YpyejDi+l5wGphZxWy8P3laLxiX27Pmd3vG2P+kmWrAgMBAAGj
|
||||
gaYwgaMwHQYDVR0OBBYEFIgnFwmpthhgi+zruvZHWcVSVKO3MA8GA1UdEwEB/wQF
|
||||
MAMBAf8wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wLgYDVR0fBCcw
|
||||
JTAjoCGgH4YdaHR0cDovL2NybC5hcHBsZS5jb20vcm9vdC5jcmwwDgYDVR0PAQH/
|
||||
BAQDAgGGMBAGCiqGSIb3Y2QGAgEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQBPz+9Z
|
||||
viz1smwvj+4ThzLoBTWobot9yWkMudkXvHcs1Gfi/ZptOllc34MBvbKuKmFysa/N
|
||||
w0Uwj6ODDc4dR7Txk4qjdJukw5hyhzs+r0ULklS5MruQGFNrCk4QttkdUGwhgAqJ
|
||||
TleMa1s8Pab93vcNIx0LSiaHP7qRkkykGRIZbVf1eliHe2iK5IaMSuviSRSqpd1V
|
||||
AKmuu0swruGgsbwpgOYJd+W+NKIByn/c4grmO7i77LpilfMFY0GCzQ87HUyVpNur
|
||||
+cmV6U/kTecmmYHpvPm0KdIBembhLoz2IYrF+Hjhga6/05Cdqa3zr/04GpZnMBxR
|
||||
pVzscYqCtGwPDBUfMIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQsw
|
||||
CQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUg
|
||||
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0Ew
|
||||
HhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzET
|
||||
MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv
|
||||
biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3
|
||||
DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne
|
||||
+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjcz
|
||||
y8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQ
|
||||
Z48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCS
|
||||
C7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINB
|
||||
hzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIB
|
||||
djAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9Bp
|
||||
R5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/
|
||||
CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcC
|
||||
ARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCB
|
||||
thqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFz
|
||||
c3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJk
|
||||
IHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5
|
||||
IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3
|
||||
DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizU
|
||||
sZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJ
|
||||
fBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr
|
||||
1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltk
|
||||
wGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIq
|
||||
xw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUhMYIByzCCAccCAQEwgaMwgZYxCzAJ
|
||||
BgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBX
|
||||
b3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29y
|
||||
bGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
|
||||
dHkCCA7rV4fnngmNMAkGBSsOAwIaBQAwDQYJKoZIhvcNAQEBBQAEggEAasPtnide
|
||||
NWyfUtewW9OSgcQA8pW+5tWMR0469cBPZR84uJa0gyfmPspySvbNOAwnrwzZHYLa
|
||||
ujOxZLip4DUw4F5s3QwUa3y4BXpF4J+NSn9XNvxNtnT/GcEQtCuFwgJ0o3F0ilhv
|
||||
MTHrwiwyx/vr+uNDqlORK8lfK+1qNp+A/kzh8eszMrn4JSeTh9ZYxLHE56WkTQGD
|
||||
VZXl0gKgxSOmDrcp1eQxdlymzrPv9U60wUJ0bkPfrU9qZj3mJrmrkQk61JTe3j6/
|
||||
QfjfFBG9JG2mUmYQP1KQ3SypGHzDW8vngvsGu//tNU0NFfOqQu4bYU4VpQl0nPtD
|
||||
4B85NkrgvQsWAQ==
|
||||
-----END PKCS7-----`
|
||||
|
|
Загрузка…
Ссылка в новой задаче