Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add atca-gen-cert - command to generate ATCA key and cert in one go
A command to generate ATCA key and cert in one go: ``` $ mos atca-gen-cert 1 cert1.crt.pem --subject=CN="OMG WTF" --cert-days=3650 --ca-cert-file ~/ca-box/data/ca.crt --ca-key-file ~/ca-box/data/ca.key --dry-run=false Using port /dev/ttyUSB0 Signing certificate: Using existing cert: /home/rojer/ca-box/data/ca.crt Using existing key : /home/rojer/ca-box/data/ca.key (ECDSA) ATECC608A rev 0x6002 S/N 0x0123f196b09f52ceee, config is locked, data is locked Generated new ECC key on slot 1 Subject: CN=OMG WTF Writing certificate to cert1.crt.pem... Uploading cert1.crt.pem (407 bytes)... $ ``` This generates new private key in slot 1, issues a certificate (signed by ca.crt/ca.key) and uploads it to the device. Shameless plug: https://github.com/rojer/ca-box for a simple "CA in a box" CL: mos: Add atca-gen-cert - command to generate ATCA key and cert in one go PUBLISHED_FROM=cba91fe4bd34938122fb124ab93948110d7c5a9d
- Loading branch information
1 parent
e4ba5c1
commit d896418
Showing
8 changed files
with
545 additions
and
127 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package atca | ||
|
||
import ( | ||
"context" | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"encoding/base64" | ||
"math/big" | ||
|
||
"github.com/cesanta/errors" | ||
|
||
"cesanta.com/common/go/ourutil" | ||
"cesanta.com/mos/dev" | ||
) | ||
|
||
func GetPubKey(ctx context.Context, slot int, dc dev.DevConn) (*ecdsa.PublicKey, error) { | ||
slot64 := int64(slot) | ||
req := &GetPubKeyArgs{Slot: &slot64} | ||
var r GetPubKeyResult | ||
if err := dc.Call(ctx, "ATCA.GetPubKey", req, &r); err != nil { | ||
return nil, errors.Annotatef(err, "GetPubKey") | ||
} | ||
if r.Pubkey == nil { | ||
return nil, errors.New("no public key in response") | ||
} | ||
pubKeyData, err := base64.StdEncoding.DecodeString(*r.Pubkey) | ||
if err != nil { | ||
return nil, errors.Annotatef(err, "failed to decode pub key data") | ||
} | ||
if len(pubKeyData) != PublicKeySize { | ||
return nil, errors.Errorf("expected %d bytes, got %d", PublicKeySize, len(pubKeyData)) | ||
} | ||
pubKey := &ecdsa.PublicKey{ | ||
Curve: elliptic.P256(), | ||
X: big.NewInt(0).SetBytes(pubKeyData[0:32]), | ||
Y: big.NewInt(0).SetBytes(pubKeyData[32:64]), | ||
} | ||
return pubKey, nil | ||
} | ||
|
||
func GenKey(ctx context.Context, slot int, dryRun bool, dc dev.DevConn) (*ecdsa.PublicKey, error) { | ||
slot64 := int64(slot) | ||
req := &GenKeyArgs{Slot: &slot64} | ||
|
||
if dryRun { | ||
ourutil.Reportf("This is a dry run, would have sent the following request:\n\n"+ | ||
"GenKey %s\n\n"+ | ||
"Set --dry-run=false to confirm.", | ||
JSONStr(*req)) | ||
return nil, nil | ||
} | ||
|
||
var r GenKeyResult | ||
if err := dc.Call(ctx, "ATCA.GenKey", req, &r); err != nil { | ||
return nil, errors.Annotatef(err, "GenKey") | ||
} | ||
|
||
if r.Pubkey == nil { | ||
return nil, errors.New("no public key in response") | ||
} | ||
|
||
pubKeyData, err := base64.StdEncoding.DecodeString(*r.Pubkey) | ||
if err != nil { | ||
return nil, errors.Annotatef(err, "failed to decode pub key data") | ||
} | ||
if len(pubKeyData) != PublicKeySize { | ||
return nil, errors.Errorf("expected %d bytes, got %d", PublicKeySize, len(pubKeyData)) | ||
} | ||
ourutil.Reportf("Generated new ECC key on slot %d", slot) | ||
pubKey := &ecdsa.PublicKey{ | ||
Curve: elliptic.P256(), | ||
X: big.NewInt(0).SetBytes(pubKeyData[0:32]), | ||
Y: big.NewInt(0).SetBytes(pubKeyData[32:64]), | ||
} | ||
return pubKey, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
package x509utils | ||
|
||
// Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com) | ||
// Portions copyright (c) 2015-2016 go-ldap Authors | ||
// | ||
// From https://github.com/go-ldap/ldap/blob/master/dn.go | ||
// Lisense: MIT | ||
|
||
import ( | ||
"bytes" | ||
"crypto/x509/pkix" | ||
"encoding/asn1" | ||
"encoding/hex" | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/cesanta/errors" | ||
) | ||
|
||
var attributeTypeNames = map[string]asn1.ObjectIdentifier{ | ||
"CN": asn1.ObjectIdentifier{2, 5, 4, 3}, | ||
"SERIALNUMBER": asn1.ObjectIdentifier{2, 5, 4, 5}, | ||
"C": asn1.ObjectIdentifier{2, 5, 4, 6}, | ||
"L": asn1.ObjectIdentifier{2, 5, 4, 7}, | ||
"ST": asn1.ObjectIdentifier{2, 5, 4, 8}, | ||
"STREET": asn1.ObjectIdentifier{2, 5, 4, 9}, | ||
"O": asn1.ObjectIdentifier{2, 5, 4, 10}, | ||
"OU": asn1.ObjectIdentifier{2, 5, 4, 11}, | ||
"POSTALCODE": asn1.ObjectIdentifier{2, 5, 4, 17}, | ||
} | ||
|
||
// AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514 | ||
type AttributeTypeAndValue struct { | ||
// Type is the attribute type | ||
Type string | ||
// Value is the attribute value | ||
Value string | ||
} | ||
|
||
// RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514 | ||
type RelativeDN struct { | ||
Attributes []*AttributeTypeAndValue | ||
} | ||
|
||
// DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514 | ||
type DN struct { | ||
RDNs []*RelativeDN | ||
} | ||
|
||
// ParseDN returns a distinguishedName or an error | ||
func ParseDN(str string) (*pkix.Name, error) { | ||
dn := new(DN) | ||
dn.RDNs = make([]*RelativeDN, 0) | ||
rdn := new(RelativeDN) | ||
rdn.Attributes = make([]*AttributeTypeAndValue, 0) | ||
buffer := bytes.Buffer{} | ||
attribute := new(AttributeTypeAndValue) | ||
escaping := false | ||
|
||
unescapedTrailingSpaces := 0 | ||
stringFromBuffer := func() string { | ||
s := buffer.String() | ||
s = s[0 : len(s)-unescapedTrailingSpaces] | ||
buffer.Reset() | ||
unescapedTrailingSpaces = 0 | ||
return s | ||
} | ||
|
||
for i := 0; i < len(str); i++ { | ||
char := str[i] | ||
switch { | ||
case escaping: | ||
unescapedTrailingSpaces = 0 | ||
escaping = false | ||
switch char { | ||
case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\': | ||
buffer.WriteByte(char) | ||
continue | ||
} | ||
// Not a special character, assume hex encoded octet | ||
if len(str) == i+1 { | ||
return nil, errors.New("got corrupted escaped character") | ||
} | ||
|
||
dst := []byte{0} | ||
n, err := hex.Decode([]byte(dst), []byte(str[i:i+2])) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to decode escaped character: %s", err) | ||
} else if n != 1 { | ||
return nil, fmt.Errorf("expected 1 byte when un-escaping, got %d", n) | ||
} | ||
buffer.WriteByte(dst[0]) | ||
i++ | ||
case char == '\\': | ||
unescapedTrailingSpaces = 0 | ||
escaping = true | ||
case char == '=': | ||
attribute.Type = stringFromBuffer() | ||
// Special case: If the first character in the value is # the | ||
// following data is BER encoded so we can just fast forward | ||
// and decode. | ||
if len(str) > i+1 && str[i+1] == '#' { | ||
return nil, errors.New("BER values not supported") | ||
/* | ||
i += 2 | ||
index := strings.IndexAny(str[i:], ",+") | ||
data := str | ||
if index > 0 { | ||
data = str[i : i+index] | ||
} else { | ||
data = str[i:] | ||
} | ||
rawBER, err := enchex.DecodeString(data) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to decode BER encoding: %s", err) | ||
} | ||
packet, err := ber.DecodePacketErr(rawBER) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to decode BER packet: %s", err) | ||
} | ||
buffer.WriteString(packet.Data.String()) | ||
i += len(data) - 1 | ||
*/ | ||
} | ||
case char == ',' || char == '+': | ||
// We're done with this RDN or value, push it | ||
if len(attribute.Type) == 0 { | ||
return nil, errors.New("incomplete type, value pair") | ||
} | ||
attribute.Value = stringFromBuffer() | ||
rdn.Attributes = append(rdn.Attributes, attribute) | ||
attribute = new(AttributeTypeAndValue) | ||
if char == ',' { | ||
dn.RDNs = append(dn.RDNs, rdn) | ||
rdn = new(RelativeDN) | ||
rdn.Attributes = make([]*AttributeTypeAndValue, 0) | ||
} | ||
case char == ' ' && buffer.Len() == 0: | ||
// ignore unescaped leading spaces | ||
continue | ||
default: | ||
if char == ' ' { | ||
// Track unescaped spaces in case they are trailing and we need to remove them | ||
unescapedTrailingSpaces++ | ||
} else { | ||
// Reset if we see a non-space char | ||
unescapedTrailingSpaces = 0 | ||
} | ||
buffer.WriteByte(char) | ||
} | ||
} | ||
if buffer.Len() > 0 { | ||
if len(attribute.Type) == 0 { | ||
return nil, errors.New("DN ended with incomplete type, value pair") | ||
} | ||
attribute.Value = stringFromBuffer() | ||
rdn.Attributes = append(rdn.Attributes, attribute) | ||
dn.RDNs = append(dn.RDNs, rdn) | ||
} | ||
return dn.ToName() | ||
} | ||
|
||
func (dn *DN) ToName() (*pkix.Name, error) { | ||
var ns pkix.RDNSequence | ||
for _, rdn := range dn.RDNs { | ||
var s pkix.RelativeDistinguishedNameSET | ||
for _, a := range rdn.Attributes { | ||
oid, ok := attributeTypeNames[a.Type] | ||
if !ok { | ||
for _, p := range strings.Split(a.Type, ".") { | ||
pi, err := strconv.Atoi(p) | ||
if err != nil { | ||
return nil, errors.Errorf("invalid attribute type %q", a.Type) | ||
} | ||
oid = append(oid, pi) | ||
} | ||
} | ||
s = append(s, pkix.AttributeTypeAndValue{ | ||
Type: oid, | ||
Value: a.Value, | ||
}) | ||
} | ||
ns = append(ns, s) | ||
} | ||
var n pkix.Name | ||
n.FillFromRDNSequence(&ns) | ||
return &n, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package x509utils | ||
|
||
import ( | ||
"crypto/ecdsa" | ||
"crypto/x509" | ||
"encoding/pem" | ||
"io" | ||
"os" | ||
|
||
"github.com/cesanta/errors" | ||
|
||
"cesanta.com/common/go/ourutil" | ||
) | ||
|
||
func WritePEM(derBytes []byte, blockType string, outputFileName string) error { | ||
var out io.Writer | ||
switch outputFileName { | ||
case "": | ||
out = os.Stdout | ||
case "-": | ||
out = os.Stdout | ||
case "--": | ||
out = os.Stderr | ||
default: | ||
f, err := os.OpenFile(outputFileName, os.O_RDWR|os.O_CREATE, 0644) | ||
if err != nil { | ||
return errors.Annotatef(err, "failed to open %s for writing", outputFileName) | ||
} | ||
out = f | ||
defer func() { | ||
f.Close() | ||
ourutil.Reportf("Wrote %s", outputFileName) | ||
}() | ||
} | ||
pem.Encode(out, &pem.Block{Type: blockType, Bytes: derBytes}) | ||
return nil | ||
} | ||
|
||
func WritePubKey(pubKey *ecdsa.PublicKey, outputFileName string) error { | ||
pubKeyDERBytes, err := x509.MarshalPKIXPublicKey(pubKey) | ||
if err != nil { | ||
return errors.Annotatef(err, "failed to marshal public key") | ||
} | ||
return WritePEM(pubKeyDERBytes, "PUBLIC KEY", outputFileName) | ||
} |