Added a dedicated revocation subpackage
The newly created dedicated revocation package is used to encode and decode revocation information. Signing and verification can now use the same structures. While the InfoArchival stucsture can now be created this stucture is currenlty not embedded into the document. Test cases for obtaining and embedding revocation information into the InfoArchival struct are also availible.
This commit is contained in:
55
revocation/revocation.go
Normal file
55
revocation/revocation.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package revocation
|
||||
|
||||
import (
|
||||
"encoding/asn1"
|
||||
"crypto/x509"
|
||||
)
|
||||
|
||||
// InfoArchival is the pkcs7 container containing the revocation information for
|
||||
// all embedded certificates.
|
||||
//
|
||||
// Currently the internal structure is exposed but I don't like to expose the
|
||||
// asn1.RawValue objects. We can probably make them private and expose the
|
||||
// information with functions.
|
||||
type InfoArchival struct {
|
||||
CRL CRL `asn1:"tag:0,optional,explicit"`
|
||||
OCSP OCSP `asn1:"tag:1,optional,explicit"`
|
||||
Other Other `asn1:"tag:2,optional,explicit"`
|
||||
}
|
||||
|
||||
// AddCRL is used to embed an CRL to revocation.InfoArchival object. You directly
|
||||
// pass the bytes of a downloaded CRL to this function.
|
||||
func (r *InfoArchival) AddCRL(b []byte) error {
|
||||
r.CRL = append(r.CRL, asn1.RawValue{FullBytes: b})
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddOCSP is used to embed the raw bytes of an OCSP response.
|
||||
func (r *InfoArchival) AddOCSP(b []byte) error {
|
||||
r.OCSP = append(r.OCSP, asn1.RawValue{FullBytes: b})
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRevoked checks if there is a status inclded for the certificate and returns
|
||||
// true if the certificate is marked as revoked.
|
||||
//
|
||||
// TODO: We should report if there is no CRL or OCSP response embeded for this certificate
|
||||
// TODO: Information about the revocation (time, reason, etc) must be extractable
|
||||
func (r *InfoArchival) IsRevoked(c *x509.Certificate) bool {
|
||||
// check the crl and ocsp to see if this certificate is revoked
|
||||
return true
|
||||
}
|
||||
|
||||
// CRL contains the raw bytes of a pkix.CertificateList and can be parsed with
|
||||
// x509.PParseCRL.
|
||||
type CRL []asn1.RawValue
|
||||
|
||||
// OCSP contains the raw bytes of an OCSP response and can be parsed with
|
||||
// x/crypto/ocsp.ParseResponse
|
||||
type OCSP []asn1.RawValue
|
||||
|
||||
// ANS.1 Object OtherRevInfo
|
||||
type Other struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
Value []byte
|
||||
}
|
88
sign/revocation.go
Normal file
88
sign/revocation.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"bitbucket.org/digitorus/pdfsign/revocation"
|
||||
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
func embedOCSPRevocationStatus(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error {
|
||||
req, err := ocsp.CreateRequest(cert, issuer, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ocspUrl := fmt.Sprintf("%s/%s", strings.TrimRight(cert.OCSPServer[0], "/"),
|
||||
base64.StdEncoding.EncodeToString(req))
|
||||
resp, err := http.Get(ocspUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if we got a valid OCSP response
|
||||
_, err = ocsp.ParseResponseForCert(body, cert, issuer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.AddOCSP(body)
|
||||
return nil
|
||||
}
|
||||
|
||||
// embedCRLRevocationStatus requires an issuer as it needs to implement the
|
||||
// the interface, a nil argment might be given if the issuer is not known.
|
||||
func embedCRLRevocationStatus(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error {
|
||||
resp, err := http.Get(cert.CRLDistributionPoints[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: verify crl and certificate before embedding
|
||||
i.AddCRL(body)
|
||||
return nil
|
||||
}
|
||||
|
||||
func embedRevocationStatus(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error {
|
||||
// For each certificate a revoction status needs to be included, this can be done
|
||||
// by embedding a CRL or OCSP response. In most cases an OCSP response is smaller
|
||||
// to embed in the document but and empty CRL (often seen of dediced high volume
|
||||
// hirachies) can be smaller.
|
||||
//
|
||||
// There have been some reports that the usage of a CRL would result in a better
|
||||
// compatibilty.
|
||||
//
|
||||
// TODO: Find and embed link about compatibilty
|
||||
// TODO: Implement revocation status caching (required for higher volume signing)
|
||||
|
||||
// using an OCSP server
|
||||
if len(cert.OCSPServer) > 0 {
|
||||
embedOCSPRevocationStatus(cert, issuer, i)
|
||||
return nil
|
||||
}
|
||||
|
||||
// using a crl
|
||||
if len(cert.CRLDistributionPoints) > 0 {
|
||||
embedCRLRevocationStatus(cert, issuer, i)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("certificate contains no information to check status")
|
||||
}
|
119
sign/revocation_test.go
Normal file
119
sign/revocation_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
|
||||
"bitbucket.org/digitorus/pdfsign/revocation"
|
||||
)
|
||||
|
||||
const certPem = `-----BEGIN CERTIFICATE-----
|
||||
MIIGKDCCBRCgAwIBAgIMW8J4m7huCPO5f+wbMA0GCSqGSIb3DQEBCwUAMEsxCzAJ
|
||||
BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSEwHwYDVQQDExhH
|
||||
bG9iYWxTaWduIENBIDIgZm9yIEFBVEwwHhcNMTcwNzA2MDk0MDUyWhcNMjAwNzA2
|
||||
MDk0MDUyWjCBpzELMAkGA1UEBhMCR0IxDTALBgNVBAgTBEtlbnQxEjAQBgNVBAcT
|
||||
CU1haWRzdG9uZTEfMB0GA1UEChMWR01PIEdsb2JhbFNpZ24gTGltaXRlZDEfMB0G
|
||||
A1UEAxMWUGF1bCBWYW4gQnJvdXdlcnNoYXZlbjEzMDEGCSqGSIb3DQEJARYkcGF1
|
||||
bC52YW5icm91d2Vyc2hhdmVuQGdsb2JhbHNpZ24uY29tMIIBIjANBgkqhkiG9w0B
|
||||
AQEFAAOCAQ8AMIIBCgKCAQEAr5jbAIZDjkWngxlwJqneE9VEDTvmMIGwvgy71g5j
|
||||
k+igHxB6tTfaqGD87oIm2wcrlZHpPJG9n2Rh9FhFmvx8ZXiceNI9Ks5Ho5iYNFUk
|
||||
y2JuVfFPxtp6amqpLzM5HZUePgu1Gdy1Zn1PUajii7paFPuhdemcA9DdTAQ1GsDv
|
||||
C9MZ2D5sKM0hLCRePCzJ3TeQmHefFrC0XQ7u2i7LDD990URiFz7WNq2tSDJwBe/6
|
||||
1tMewpekmtE5X43PqzgcyGBsDJqAKcthsLQnhqrIryuwE2bEhP/FxQrHkT+f/OkK
|
||||
vwAjB9gYgJRNoMFUwXLW789JsOkbCqepoWsg1PnWrAbAMQIDAQABo4ICrTCCAqkw
|
||||
DgYDVR0PAQH/BAQDAgeAMIGIBggrBgEFBQcBAQR8MHowQQYIKwYBBQUHMAKGNWh0
|
||||
dHA6Ly9zZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzYWF0bDJzaGEyZzIu
|
||||
Y3J0MDUGCCsGAQUFBzABhilodHRwOi8vb2NzcDIuZ2xvYmFsc2lnbi5jb20vZ3Nh
|
||||
YXRsMnNoYTJnMjCB2wYDVR0gBIHTMIHQMIHNBgsrBgEEAaAyASgeAjCBvTCBhgYI
|
||||
KwYBBQUHAgIwegx4VGhpcyBjZXJ0aWZpY2F0ZSBoYXMgYmVlbiBpc3N1ZWQgaW4g
|
||||
YWNjb3JkYW5jZSB3aXRoIHRoZSBHbG9iYWxTaWduIENQUyBsb2NhdGVkIGF0IGh0
|
||||
dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMDIGCCsGAQUFBwIB
|
||||
FiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzAJBgNVHRME
|
||||
AjAAMD8GA1UdHwQ4MDYwNKAyoDCGLmh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20v
|
||||
Z3MvZ3NhYXRsMnNoYTJnMi5jcmwwLwYDVR0RBCgwJoEkcGF1bC52YW5icm91d2Vy
|
||||
c2hhdmVuQGdsb2JhbHNpZ24uY29tMFwGCiqGSIb3LwEBCQEETjBMAgEBhkRodHRw
|
||||
Oi8vYWF0bC10aW1lc3RhbXAuZ2xvYmFsc2lnbi5jb20vdHNhL2FvaGZld2F0MjM4
|
||||
OTUzNWZuYXNnbmxnNW0yMwEBADATBgNVHSUEDDAKBggrBgEFBQcDBDAdBgNVHQ4E
|
||||
FgQUtimjVds00OgBZ747tqkKbZJeCCowHwYDVR0jBBgwFoAUxRNOzofGiRsj6EDj
|
||||
dTKbA3A67+8wDQYJKoZIhvcNAQELBQADggEBACBUFVwUKVpOWt1eLf7lKKPfVhEL
|
||||
9QrkAkV/UZPMsDwBDIJhphqjCqfbJVTgybm79gUCJiwbarCYOHRgFAdNTPEvEcT0
|
||||
+XwR6WZcDdfQAtaHfO6X9ExgJv93txoFVcpYLY1hR3o6QdP4VQSDhRTv3bM1j/WC
|
||||
mcCoiIQz28Y8L+8rRx5J7JAgYpupoU/8sCpidBMhYAGF5p8Z8p0LbqvZndRHaVqp
|
||||
yXQ0kYj1n45it5FXsKECWZKTx0v4IBySJY3RGpF+5cpPUYulJfINBg7nj7aQG/Uv
|
||||
qtyxnVAG3W4pTHWd/0Gyc3lrgRtyZy+b9DaxHZ/N6HHNgnRHB4PUkektpX4=
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
const issuerPem = `-----BEGIN CERTIFICATE-----
|
||||
MIIEpjCCA46gAwIBAgIORea3r9ymcb22XRTz2sAwDQYJKoZIhvcNAQELBQAwVzEL
|
||||
MAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExLTArBgNVBAMT
|
||||
JEdsb2JhbFNpZ24gQ0EgZm9yIEFBVEwgLSBTSEEyNTYgLSBHMjAeFw0xNDEyMTAw
|
||||
MDAwMDBaFw0yNDEyMTAwMDAwMDBaMEsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBH
|
||||
bG9iYWxTaWduIG52LXNhMSEwHwYDVQQDExhHbG9iYWxTaWduIENBIDIgZm9yIEFB
|
||||
VEwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJS4vhNfaSmXtX7bWy
|
||||
hVhBGqisbqgnX9/K5psHMrkwm10pnicCSmvb6nvgMAPbRPyfpGHj5ArrDli6rCDR
|
||||
CLR1tjied/6AxQCCPgyvDyEwDWxzytVHkCldzEHjHmc1kL0zI7aQfNrD25xAUnHa
|
||||
X2jBm71filgduyQBfuLJLlL/NGh46X6eI9xpqBmqwBFa6wHPisnwkAB22CQB/OY3
|
||||
mnlhLCJmrEL9fGPRDQnc6w849ws3nkKISkEPmTyfUAJUkEgCxOjfiwoNSFjZ99lA
|
||||
K+ujj90dqYU3mJ7dTEHA2+dFrdvVsoyA4JZBu7utxe2rJgpifO3B/L+f4Cat12kA
|
||||
2IqtAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB
|
||||
/wIBADAdBgNVHQ4EFgQUxRNOzofGiRsj6EDjdTKbA3A67+8wHwYDVR0jBBgwFoAU
|
||||
YMLxUj6tjBPc28oO+grmKiyZS9gwgYYGCCsGAQUFBwEBBHoweDA0BggrBgEFBQcw
|
||||
AYYoaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzYWF0bHNoYTJnMjBABggr
|
||||
BgEFBQcwAoY0aHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNvbS9jYWNlcnQvZ3Nh
|
||||
YXRsc2hhMmcyLmNydDA+BgNVHR8ENzA1MDOgMaAvhi1odHRwOi8vY3JsLmdsb2Jh
|
||||
bHNpZ24uY29tL2dzL2dzYWF0bHNoYTJnMi5jcmwwRwYDVR0gBEAwPjA8BgRVHSAA
|
||||
MDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9z
|
||||
aXRvcnkvMA0GCSqGSIb3DQEBCwUAA4IBAQAkH9vI0bJPzbtf5ikdFb5Kc4L9/IIZ
|
||||
GPeVbS3UPl8x4WPzmZHwGvSeG53DDcJ9weGHO4PORBgBT5KDZEt7CKXd4vt83xw8
|
||||
P1kYvvjv4E+8R8VD3hP48zkygHMR3JtBdMPkbNbE10TCb4XmQPpkwGIVhaD7ojtS
|
||||
+4mPjVts6ZZzbnHI42CbYwdaOf2W8IUu0b1w4T7T5YPfi8rSKwQxIKibFG1mSsOC
|
||||
vG9tDxJVJUNdiWTPoHGn+n+3qeJvPRgHhicZ+ivOqqmLQSNgtp1WdQ+uJmUqU7EY
|
||||
htN+laG7bS/8xGTPothL9Abgd/9L3X0KKGUDCdcpzRuy20CI7E4uygD8
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
func TestEmbedRevocationStatus(t *testing.T) {
|
||||
var ia revocation.InfoArchival
|
||||
|
||||
err := embedRevocationStatus(pemToCert(certPem), pemToCert(issuerPem), &ia)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err.Error())
|
||||
}
|
||||
|
||||
if len(ia.OCSP) != 1 {
|
||||
t.Errorf("Expected one OCSP status")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbedOCSPRevocationStatus(t *testing.T) {
|
||||
var ia revocation.InfoArchival
|
||||
|
||||
err := embedOCSPRevocationStatus(pemToCert(certPem), pemToCert(issuerPem), &ia)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err.Error())
|
||||
}
|
||||
|
||||
if len(ia.OCSP) != 1 {
|
||||
t.Errorf("Expected one OCSP status")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbedCRLRevocationStatus(t *testing.T) {
|
||||
var ia revocation.InfoArchival
|
||||
|
||||
err := embedCRLRevocationStatus(pemToCert(certPem), nil, &ia)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err.Error())
|
||||
}
|
||||
|
||||
if len(ia.CRL) != 1 {
|
||||
t.Errorf("Expected one CRL")
|
||||
}
|
||||
}
|
||||
|
||||
func pemToCert(p string) *x509.Certificate {
|
||||
block, _ := pem.Decode([]byte(p))
|
||||
cert, _ := x509.ParseCertificate(block.Bytes)
|
||||
|
||||
return cert
|
||||
}
|
@@ -11,26 +11,13 @@ import (
|
||||
"time"
|
||||
|
||||
"bitbucket.org/digitorus/pdf"
|
||||
"bitbucket.org/digitorus/pdfsign/revocation"
|
||||
"github.com/digitorus/pkcs7"
|
||||
"github.com/digitorus/timestamp"
|
||||
"go/src/log"
|
||||
"log"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
type RevocationInfoArchival struct {
|
||||
CRL RevCRL `asn1:"tag:0,optional,explicit"`
|
||||
OCSP RevOCSP `asn1:"tag:1,optional,explicit"`
|
||||
OtherRevInfo OtherRevInfo `asn1:"tag:2,optional,explicit"`
|
||||
}
|
||||
|
||||
type RevCRL []asn1.RawValue
|
||||
type RevOCSP []asn1.RawValue
|
||||
|
||||
type OtherRevInfo struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
Value []byte
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Error string
|
||||
|
||||
@@ -192,7 +179,7 @@ func Verify(file *os.File) (apiResp *Response, err error) {
|
||||
}
|
||||
|
||||
// PDF signature certificate revocation information attribute (1.2.840.113583.1.1.8)
|
||||
var revInfo RevocationInfoArchival
|
||||
var revInfo revocation.InfoArchival
|
||||
p7.UnmarshalSignedAttribute(asn1.ObjectIdentifier{1, 2, 840, 113583, 1, 1, 8}, &revInfo)
|
||||
|
||||
// Parse OCSP response
|
||||
|
Reference in New Issue
Block a user