bcrypt and null kdf done, work on ciphers next (then keytypes)
This commit is contained in:
parent
91d5e99404
commit
ff3f8243d1
2
TODO
2
TODO
@ -4,5 +4,3 @@
|
|||||||
--- ssh-rsa (sha1), rsa-sha2-256, rsa-sha2-512 (new default)
|
--- ssh-rsa (sha1), rsa-sha2-256, rsa-sha2-512 (new default)
|
||||||
- ciphers:
|
- ciphers:
|
||||||
-- 3des-cbc, aes128-cbc, aes192-cbc, aes256-cbc, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com, chacha20-poly1305@openssh.com
|
-- 3des-cbc, aes128-cbc, aes192-cbc, aes256-cbc, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com, chacha20-poly1305@openssh.com
|
||||||
- kdf
|
|
||||||
-- bcrypt_pbkdf
|
|
||||||
|
@ -651,13 +651,90 @@ pre.rouge {
|
|||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<!-- https://stackoverflow.com/a/34481639 -->
|
||||||
|
<!-- Generate a nice TOC -->
|
||||||
|
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
|
||||||
|
<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.tocify/1.9.0/javascripts/jquery.tocify.min.js"></script>
|
||||||
|
<!-- We do not need the tocify CSS because the asciidoc CSS already provides most of what we neeed -->
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tocify-header {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify-subheader {
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify ul {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify-focus {
|
||||||
|
color: #7a2518;
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify-focus > a {
|
||||||
|
color: #7a2518;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function () {
|
||||||
|
// Add a new container for the tocify toc into the existing toc so we can re-use its
|
||||||
|
// styling
|
||||||
|
$("#toc").append("<div id='generated-toc'></div>");
|
||||||
|
$("#generated-toc").tocify({
|
||||||
|
extendPage: true,
|
||||||
|
context: "#content",
|
||||||
|
highlightOnScroll: true,
|
||||||
|
hideEffect: "slideUp",
|
||||||
|
// Use the IDs that asciidoc already provides so that TOC links and intra-document
|
||||||
|
// links are the same. Anything else might confuse users when they create bookmarks.
|
||||||
|
hashGenerator: function(text, element) {
|
||||||
|
return $(element).attr("id");
|
||||||
|
},
|
||||||
|
// Smooth scrolling doesn't work properly if we use the asciidoc IDs
|
||||||
|
smoothScroll: false,
|
||||||
|
// Set to 'none' to use the tocify classes
|
||||||
|
theme: "none",
|
||||||
|
// Handle book (may contain h1) and article (only h2 deeper)
|
||||||
|
selectors: $( "#content" ).has( "h1" ).size() > 0 ? "h1,h2,h3,h4,h5" : "h2,h3,h4,h5",
|
||||||
|
ignoreSelector: ".discrete"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Switch between static asciidoc toc and dynamic tocify toc based on browser size
|
||||||
|
// This is set to match the media selectors in the asciidoc CSS
|
||||||
|
// Without this, we keep the dynamic toc even if it is moved from the side to preamble
|
||||||
|
// position which will cause odd scrolling behavior
|
||||||
|
var handleTocOnResize = function() {
|
||||||
|
if ($(document).width() < 768) {
|
||||||
|
$("#generated-toc").hide();
|
||||||
|
$(".sectlevel0").show();
|
||||||
|
$(".sectlevel1").show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#generated-toc").show();
|
||||||
|
$(".sectlevel0").hide();
|
||||||
|
$(".sectlevel1").hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(window).resize(handleTocOnResize);
|
||||||
|
handleTocOnResize();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body class="book toc2 toc-left">
|
<body class="book toc2 toc-left">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<h1>OpenSSH Key Structure Guide</h1>
|
<h1>OpenSSH Key Structure Guide</h1>
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<span id="author" class="author">brent saner <bts@square-r00t.net>, https://r00t2.io</span><br>
|
<span id="author" class="author">brent saner <bts@square-r00t.net>, https://r00t2.io</span><br>
|
||||||
<span id="revdate">Last updated 2022-03-10 00:18:45 -0500</span>
|
<span id="revdate">Last updated 2022-04-25 04:27:24 -0400</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="toc" class="toc2">
|
<div id="toc" class="toc2">
|
||||||
<div id="toctitle">Table of Contents</div>
|
<div id="toctitle">Table of Contents</div>
|
||||||
|
77
_ref/docinfo.html
Normal file
77
_ref/docinfo.html
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<!-- https://stackoverflow.com/a/34481639 -->
|
||||||
|
<!-- Generate a nice TOC -->
|
||||||
|
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
|
||||||
|
<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.tocify/1.9.0/javascripts/jquery.tocify.min.js"></script>
|
||||||
|
<!-- We do not need the tocify CSS because the asciidoc CSS already provides most of what we neeed -->
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tocify-header {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify-subheader {
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify ul {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify-focus {
|
||||||
|
color: #7a2518;
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify-focus > a {
|
||||||
|
color: #7a2518;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function () {
|
||||||
|
// Add a new container for the tocify toc into the existing toc so we can re-use its
|
||||||
|
// styling
|
||||||
|
$("#toc").append("<div id='generated-toc'></div>");
|
||||||
|
$("#generated-toc").tocify({
|
||||||
|
extendPage: true,
|
||||||
|
context: "#content",
|
||||||
|
highlightOnScroll: true,
|
||||||
|
hideEffect: "slideUp",
|
||||||
|
// Use the IDs that asciidoc already provides so that TOC links and intra-document
|
||||||
|
// links are the same. Anything else might confuse users when they create bookmarks.
|
||||||
|
hashGenerator: function(text, element) {
|
||||||
|
return $(element).attr("id");
|
||||||
|
},
|
||||||
|
// Smooth scrolling doesn't work properly if we use the asciidoc IDs
|
||||||
|
smoothScroll: false,
|
||||||
|
// Set to 'none' to use the tocify classes
|
||||||
|
theme: "none",
|
||||||
|
// Handle book (may contain h1) and article (only h2 deeper)
|
||||||
|
selectors: $( "#content" ).has( "h1" ).size() > 0 ? "h1,h2,h3,h4,h5" : "h2,h3,h4,h5",
|
||||||
|
ignoreSelector: ".discrete"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Switch between static asciidoc toc and dynamic tocify toc based on browser size
|
||||||
|
// This is set to match the media selectors in the asciidoc CSS
|
||||||
|
// Without this, we keep the dynamic toc even if it is moved from the side to preamble
|
||||||
|
// position which will cause odd scrolling behavior
|
||||||
|
var handleTocOnResize = function() {
|
||||||
|
if ($(document).width() < 768) {
|
||||||
|
$("#generated-toc").hide();
|
||||||
|
$(".sectlevel0").show();
|
||||||
|
$(".sectlevel1").show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#generated-toc").show();
|
||||||
|
$(".sectlevel0").hide();
|
||||||
|
$(".sectlevel1").hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(window).resize(handleTocOnResize);
|
||||||
|
handleTocOnResize();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
1
cipher/aes/aes128/cbc/TODO
Normal file
1
cipher/aes/aes128/cbc/TODO
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO
|
5
cipher/aes/aes128/cbc/consts.go
Normal file
5
cipher/aes/aes128/cbc/consts.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package cbc
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name string = "aes128-cbc"
|
||||||
|
)
|
10
cipher/aes/aes128/consts.go
Normal file
10
cipher/aes/aes128/consts.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package aes128
|
||||||
|
|
||||||
|
import (
|
||||||
|
`r00t2.io/sshkeys/cipher/aes`
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeySize int = 16 // in bytes; AES128 is so named for its 128-bit key, thus: 128 / 8 = 16
|
||||||
|
KdfKeySize int = KeySize + aes.IvSize
|
||||||
|
)
|
1
cipher/aes/aes128/ctr/TODO
Normal file
1
cipher/aes/aes128/ctr/TODO
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO
|
5
cipher/aes/aes128/ctr/consts.go
Normal file
5
cipher/aes/aes128/ctr/consts.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package ctr
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name string = "aes128-ctr"
|
||||||
|
)
|
1
cipher/aes/aes128/gcm/TODO
Normal file
1
cipher/aes/aes128/gcm/TODO
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO
|
5
cipher/aes/aes128/gcm/consts.go
Normal file
5
cipher/aes/aes128/gcm/consts.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package gcm
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name string = "aes128-gcm@openssh.com"
|
||||||
|
)
|
1
cipher/aes/aes192/cbc/TODO
Normal file
1
cipher/aes/aes192/cbc/TODO
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO
|
5
cipher/aes/aes192/cbc/consts.go
Normal file
5
cipher/aes/aes192/cbc/consts.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package cbc
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name string = "aes192-cbc"
|
||||||
|
)
|
10
cipher/aes/aes192/consts.go
Normal file
10
cipher/aes/aes192/consts.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package aes192
|
||||||
|
|
||||||
|
import (
|
||||||
|
`r00t2.io/sshkeys/cipher/aes`
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeySize int = 24 // in bytes; AES182 is so named for its 192-bit key, thus: 192 / 8 = 24
|
||||||
|
KdfKeySize int = KeySize + aes.IvSize
|
||||||
|
)
|
1
cipher/aes/aes192/ctr/TODO
Normal file
1
cipher/aes/aes192/ctr/TODO
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO
|
5
cipher/aes/aes192/ctr/consts.go
Normal file
5
cipher/aes/aes192/ctr/consts.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package ctr
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name string = "aes192-ctr"
|
||||||
|
)
|
1
cipher/aes/aes192/gcm/TODO
Normal file
1
cipher/aes/aes192/gcm/TODO
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO
|
5
cipher/aes/aes192/gcm/consts.go
Normal file
5
cipher/aes/aes192/gcm/consts.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package gcm
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name string = "aes192-gcm@openssh.com"
|
||||||
|
)
|
1
cipher/aes/aes256/cbc/TODO
Normal file
1
cipher/aes/aes256/cbc/TODO
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO
|
5
cipher/aes/aes256/cbc/consts.go
Normal file
5
cipher/aes/aes256/cbc/consts.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package cbc
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name string = "aes256-cbc"
|
||||||
|
)
|
10
cipher/aes/aes256/consts.go
Normal file
10
cipher/aes/aes256/consts.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package aes256
|
||||||
|
|
||||||
|
import (
|
||||||
|
`r00t2.io/sshkeys/cipher/aes`
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeySize int = 32 // in bytes; AES256 is so named for its 256-bit key, thus: 256 / 8 = 32
|
||||||
|
KdfKeySize int = KeySize + aes.IvSize
|
||||||
|
)
|
1
cipher/aes/aes256/ctr/TODO
Normal file
1
cipher/aes/aes256/ctr/TODO
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO
|
5
cipher/aes/aes256/ctr/consts.go
Normal file
5
cipher/aes/aes256/ctr/consts.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package ctr
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name string = "aes256-ctr"
|
||||||
|
)
|
1
cipher/aes/aes256/gcm/TODO
Normal file
1
cipher/aes/aes256/gcm/TODO
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO
|
5
cipher/aes/aes256/gcm/consts.go
Normal file
5
cipher/aes/aes256/gcm/consts.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package gcm
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name string = "aes256-gcm@openssh.com"
|
||||||
|
)
|
10
cipher/aes/consts.go
Normal file
10
cipher/aes/consts.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package aes
|
||||||
|
|
||||||
|
import (
|
||||||
|
`crypto/aes`
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BlockSize int = aes.BlockSize
|
||||||
|
IvSize int = aes.BlockSize
|
||||||
|
)
|
1
cipher/chacha20/poly1305/TODO
Normal file
1
cipher/chacha20/poly1305/TODO
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO
|
1
cipher/null/TODO
Normal file
1
cipher/null/TODO
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO
|
1
cipher/tripledes/cbc/TODO
Normal file
1
cipher/tripledes/cbc/TODO
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO
|
50
cipher/types.go
Normal file
50
cipher/types.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package cipher
|
||||||
|
|
||||||
|
import (
|
||||||
|
`bytes`
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cipher interface {
|
||||||
|
// Name returns the string form of the cipher name.
|
||||||
|
Name() (name string)
|
||||||
|
// NameBytes returns the Name result but in bytes, with a leading uint32 bytecount packed in.
|
||||||
|
NameBytes() (name []byte)
|
||||||
|
// BlockSize returns the blocksize of the cipher.Cipher. This is used for externally padding data for Cipher.Encrypt and Cipher.AllocateEncrypt.
|
||||||
|
BlockSize() (size int)
|
||||||
|
// KdfKeySize returns the desired/needed key size for use with kdf.KDF.
|
||||||
|
KdfKeySize() (size int)
|
||||||
|
// Setup initializes the Cipher with a given key.
|
||||||
|
Setup(key []byte) (err error)
|
||||||
|
/*
|
||||||
|
Encrypt takes plain data, either a:
|
||||||
|
- string
|
||||||
|
- raw byte slice ([]byte or []uint8)
|
||||||
|
- single byte (byte or uint8)
|
||||||
|
- *bytes.Buffer
|
||||||
|
and returns an encrypted *bytes.Buffer of data.
|
||||||
|
*/
|
||||||
|
Encrypt(data interface{}) (encrypted *bytes.Reader, err error)
|
||||||
|
// AllocateEncrypt is exactly like cipher.Cipher.Encrypt except that it includes a (NON-encrypted) uint32 prefix of byte allocation.
|
||||||
|
AllocateEncrypt(data interface{}) (encrypted *bytes.Reader, err error)
|
||||||
|
/*
|
||||||
|
Decrypt takes encrypted data, either a:
|
||||||
|
- raw byte slice ([]byte or []uint8)
|
||||||
|
- *bytes.Buffer
|
||||||
|
and returns a plain/decrypted *bytes.Buffer of data.
|
||||||
|
*/
|
||||||
|
Decrypt(data interface{}) (decrypted *bytes.Reader, err error)
|
||||||
|
// AllocatedDecrypt is exactly like cipher.Cipher.Decrypt except that it assumes that data includes a (NON-encrypted) uint32 prefix of byte allocation.
|
||||||
|
AllocatedDecrypt(data interface{}) (decrypted *bytes.Reader, err error)
|
||||||
|
// IsPlain returns true if this is a "null" cipher; i.e. no encryption is actually performed.
|
||||||
|
IsPlain() (plain bool)
|
||||||
|
/*
|
||||||
|
Pad returns padded bytes in a *bytes.Buffer according to the cipher's padding specification.
|
||||||
|
data can be one of either:
|
||||||
|
- string
|
||||||
|
- raw byte slice ([]byte or []uint8)
|
||||||
|
- single byte (byte or uint8)
|
||||||
|
- *bytes.Buffer
|
||||||
|
This is a prerequisite in some ciphers, and must be performed BEFORE encrypting.
|
||||||
|
*/
|
||||||
|
Pad(data interface{}) (paddedBuf *bytes.Reader, err error)
|
||||||
|
}
|
11
kdf/bcrypt/consts.go
Normal file
11
kdf/bcrypt/consts.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package bcrypt
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name string = "bcrypt"
|
||||||
|
// DefaultRounds is the default per OpenSSH, not per the bcrypt_pbkdf spec itself. It is recommended to use e.g. 100 rounds.
|
||||||
|
DefaultRounds uint32 = 16
|
||||||
|
// DefaultSaltLen is the default per OpenSSH, not per the bcrypt_pbkdf spec itself.
|
||||||
|
DefaultSaltLen int = 16
|
||||||
|
// DefaultKeyLen is suitable for AES256-CTR but may not be for others. TODO: revisit this and find something more flexible?
|
||||||
|
DefaultKeyLen uint32 = 48
|
||||||
|
)
|
246
kdf/bcrypt/funcs.go
Normal file
246
kdf/bcrypt/funcs.go
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
package bcrypt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/dchest/bcrypt_pbkdf"
|
||||||
|
"r00t2.io/sshkeys/internal"
|
||||||
|
`r00t2.io/sshkeys/kdf/errs`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Setup must be called before DeriveKey. It configures a .
|
||||||
|
|
||||||
|
If secret is nil or an empty byte slice, ErrNoSecret will be returned.
|
||||||
|
|
||||||
|
If salt is nil or an empty byte slice, one will be randomly generated of DefaultSaltLen length.
|
||||||
|
|
||||||
|
If rounds is 0, DefaultRounds will be used.
|
||||||
|
|
||||||
|
If keyLen is 0, DefaultKeyLen will be used.
|
||||||
|
*/
|
||||||
|
func (k *KDF) Setup(secret, salt []byte, rounds, keyLen uint32) (err error) {
|
||||||
|
|
||||||
|
if secret == nil || len(secret) == 0 {
|
||||||
|
err = errs.ErrNoSecret
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if salt == nil || len(salt) == 0 {
|
||||||
|
salt = make([]byte, DefaultSaltLen)
|
||||||
|
if _, err = rand.Read(salt); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rounds == 0 {
|
||||||
|
rounds = DefaultRounds
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyLen == 0 {
|
||||||
|
keyLen = DefaultKeyLen
|
||||||
|
}
|
||||||
|
|
||||||
|
k.secret = secret
|
||||||
|
k.salt = salt
|
||||||
|
k.rounds = rounds
|
||||||
|
k.keyLen = keyLen
|
||||||
|
k.hasSalt = true
|
||||||
|
k.hasRounds = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
SetupAuto is used to provide out-of-band configuration if the KDF options were found via kdf.UnpackKDF.
|
||||||
|
|
||||||
|
You can test this by running KDF.AutoOK.
|
||||||
|
*/
|
||||||
|
func (k *KDF) SetupAuto(secret []byte, keyLen uint32) (err error) {
|
||||||
|
|
||||||
|
if !k.AutoOK() {
|
||||||
|
err = errs.ErrUnknownKdf
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyLen == 0 {
|
||||||
|
keyLen = DefaultKeyLen
|
||||||
|
}
|
||||||
|
|
||||||
|
k.secret = secret
|
||||||
|
k.keyLen = keyLen
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
DeriveKey returns the derived key from a configured bcrypt.KDF.
|
||||||
|
|
||||||
|
It must be called *after* Setup.
|
||||||
|
*/
|
||||||
|
func (k *KDF) DeriveKey() (key []byte, err error) {
|
||||||
|
|
||||||
|
if k.secret == nil {
|
||||||
|
err = errs.ErrNoSecret
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if k.salt == nil {
|
||||||
|
err = errs.ErrNoSalt
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if k.rounds == 0 {
|
||||||
|
err = errs.ErrNoRounds
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if k.keyLen == 0 {
|
||||||
|
err = errs.ErrNoKeyLen
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.key, err = bcrypt_pbkdf.Key(k.secret, k.salt, int(k.rounds), int(k.keyLen)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key = k.key
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns bcrypt.Name.
|
||||||
|
func (k *KDF) Name() (name string) {
|
||||||
|
|
||||||
|
name = Name
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameBytes returns the byte form of bcrypt.Name with leading bytecount allocator.
|
||||||
|
func (k *KDF) NameBytes() (name []byte) {
|
||||||
|
|
||||||
|
var nb []byte
|
||||||
|
var s = k.Name()
|
||||||
|
|
||||||
|
nb = []byte(s)
|
||||||
|
|
||||||
|
name = make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(name, uint32(len(nb)))
|
||||||
|
name = append(name, nb...)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackedBytes returns block 3.0 and recursed.
|
||||||
|
func (k *KDF) PackedBytes() (buf *bytes.Reader, err error) {
|
||||||
|
|
||||||
|
var rounds = make([]byte, 4)
|
||||||
|
var packer *bytes.Reader
|
||||||
|
var w = new(bytes.Buffer)
|
||||||
|
|
||||||
|
// block 3.0.0.0 and block 3.0.0.0.0
|
||||||
|
if packer, err = internal.ReadSizeBytes(k.salt, true); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = packer.WriteTo(w); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// block 3.0.0.1
|
||||||
|
binary.BigEndian.PutUint32(rounds, k.rounds)
|
||||||
|
if _, err = w.Write(rounds); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// block 3.0
|
||||||
|
if buf, err = internal.ReadSizeBytes(w, true); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rounds returns the number of rounds used in derivation.
|
||||||
|
func (k *KDF) Rounds() (rounds uint32) {
|
||||||
|
|
||||||
|
rounds = k.rounds
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Salt returns the salt bytes.
|
||||||
|
func (k *KDF) Salt() (salt []byte) {
|
||||||
|
|
||||||
|
salt = k.salt
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
AutoOK returns true if a kdf.UnpackKDF call was able to fetch the bcrypt.KDF options successfully, in which case the caller may use bcrypt.KDF.SetupAuto.
|
||||||
|
|
||||||
|
If false, it will need to be manually configured via bcrypt.KDF.Setup.
|
||||||
|
*/
|
||||||
|
func (k *KDF) AutoOK() (ok bool) {
|
||||||
|
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
if k.salt == nil || len(k.salt) == 0 {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.rounds == 0 {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPlain indicates if this kdf.KDF actually does derivation (false) or not (true).
|
||||||
|
func (k *KDF) IsPlain() (plain bool) {
|
||||||
|
|
||||||
|
plain = false
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
AddSalt adds a salt as parsed from kdf.UnpackKDF.
|
||||||
|
|
||||||
|
If a salt has already been set, a no-op will be performed.
|
||||||
|
If you want to use a different salt, you will need to create a new bcrypt.KDF.
|
||||||
|
*/
|
||||||
|
func (k *KDF) AddSalt(salt []byte) (err error) {
|
||||||
|
|
||||||
|
if salt == nil || len(salt) == 0 {
|
||||||
|
err = errs.ErrNoSalt
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if k.hasSalt {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
k.salt = salt
|
||||||
|
k.hasSalt = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
AddRounds adds the rounds as parsed from kdf.UnpackKDF.
|
||||||
|
|
||||||
|
If the number of rounds has already been set, a no-op will be performed.
|
||||||
|
If you want to use a different number of rounds, you will need to create a new bcrypt.KDF.
|
||||||
|
*/
|
||||||
|
func (k *KDF) AddRounds(rounds uint32) (err error) {
|
||||||
|
|
||||||
|
if rounds == 0 {
|
||||||
|
err = errs.ErrNoRounds
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if k.hasRounds {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
k.rounds = rounds
|
||||||
|
k.hasRounds = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
25
kdf/bcrypt/types.go
Normal file
25
kdf/bcrypt/types.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package bcrypt
|
||||||
|
|
||||||
|
/*
|
||||||
|
BcryptPbkdf combines bcrypt hashing algorithm with PBKDF2 key derivation.
|
||||||
|
|
||||||
|
(bcrypt) https://www.usenix.org/legacy/events/usenix99/provos/provos_html/node1.html
|
||||||
|
(PBKDF2) https://datatracker.ietf.org/doc/html/rfc2898
|
||||||
|
http://www.tedunangst.com/flak/post/bcrypt-pbkdf
|
||||||
|
*/
|
||||||
|
type KDF struct {
|
||||||
|
// salt is used to salt the hash for each round in rounds.
|
||||||
|
salt []byte
|
||||||
|
// rounds controls how many iterations of salting/hashing is done.
|
||||||
|
rounds uint32
|
||||||
|
// keyLen is how long the derived key should be in bytes.
|
||||||
|
keyLen uint32
|
||||||
|
// secret is the "passphrase" used to seed the key creation.
|
||||||
|
secret []byte
|
||||||
|
// key is used to store the derived key.
|
||||||
|
key []byte
|
||||||
|
// hasSalt is true if a salt has been set.
|
||||||
|
hasSalt bool
|
||||||
|
// hasRounds is true if a number of rounds have been set.
|
||||||
|
hasRounds bool
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
package kdf
|
|
||||||
|
|
||||||
const (
|
|
||||||
BcryptPbkdfName string = "bcrypt"
|
|
||||||
// BcryptPbkdfDefaultRounds is the default per OpenSSH, not per the bcrypt_pbkdf spec itself. It is recommended to use e.g. 100 rounds.
|
|
||||||
BcryptPbkdfDefaultRounds uint32 = 16
|
|
||||||
// BcryptPbkdfDefaultSaltLen is the default per OpenSSH, not per the bcrypt_pbkdf spec itself.
|
|
||||||
BcryptPbkdfDefaultSaltLen int = 16
|
|
||||||
// BcryptPbkdfDefaultKeyLen is suitable for AES256-CTR but may not be for others. TODO: revisit this and find something more flexible.
|
|
||||||
BcryptPbkdfDefaultKeyLen uint32 = 48
|
|
||||||
)
|
|
@ -1,5 +0,0 @@
|
|||||||
package kdf
|
|
||||||
|
|
||||||
const (
|
|
||||||
NullName string = "none"
|
|
||||||
)
|
|
@ -1,4 +1,4 @@
|
|||||||
package kdf
|
package errs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
23
kdf/funcs.go
23
kdf/funcs.go
@ -2,6 +2,10 @@ package kdf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
`r00t2.io/sshkeys/kdf/bcrypt`
|
||||||
|
`r00t2.io/sshkeys/kdf/errs`
|
||||||
|
`r00t2.io/sshkeys/kdf/null`
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -12,12 +16,12 @@ import (
|
|||||||
func GetKDF(name string) (k KDF, err error) {
|
func GetKDF(name string) (k KDF, err error) {
|
||||||
|
|
||||||
switch strings.ToLower(name) {
|
switch strings.ToLower(name) {
|
||||||
case BcryptPbkdfName:
|
case bcrypt.Name:
|
||||||
k = &BcryptPbkdf{}
|
k = &bcrypt.KDF{}
|
||||||
case NullName, "":
|
case null.Name, "":
|
||||||
k = &Null{}
|
k = &null.KDF{}
|
||||||
default:
|
default:
|
||||||
err = ErrUnknownKdf
|
err = errs.ErrUnknownKdf
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,18 +29,21 @@ func GetKDF(name string) (k KDF, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
UnpackKDF returns a KDF from raw data as packed in a private key's bytes.
|
UnpackKDF returns a kdf.KDF from raw data as packed in a private key's bytes.
|
||||||
|
|
||||||
KDF.Setup must be called on the resulting KDF.
|
kdf.KDF.Setup must be called on the resulting kdf.KDF.
|
||||||
|
|
||||||
data can be:
|
data can be:
|
||||||
|
|
||||||
- a []byte, which can be:
|
- a []byte, which can be:
|
||||||
- the full private key bytes
|
- the full private key bytes
|
||||||
- KDF section (e.g. _ref/format.ed25519 2.0 + (3.0 to 3.0.0.1) or 2.0.0 + (3.0 to 3.0.0.1))
|
- KDF section (e.g. ED25519 private key block 2.0 + (block 3.0 to block 3.0.0.1) OR
|
||||||
|
block 2.0.0 + (block 3.0 to block 3.0.0.1))
|
||||||
- a *bytes.Buffer, which supports the same as above
|
- a *bytes.Buffer, which supports the same as above
|
||||||
*/
|
*/
|
||||||
func UnpackKDF(data interface{}) (k KDF, err error) {
|
func UnpackKDF(data interface{}) (k KDF, err error) {
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,226 +0,0 @@
|
|||||||
package kdf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
|
|
||||||
bcryptPbkdf "github.com/dchest/bcrypt_pbkdf"
|
|
||||||
"r00t2.io/sshkeys/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Setup must be called before DeriveKey. It configures a BcryptPbkdf.
|
|
||||||
|
|
||||||
If secret is nil or an empty byte slice, ErrNoSecret will be returned.
|
|
||||||
|
|
||||||
If salt is nil or an empty byte slice, one will be randomly generated of BcryptPbkdfDefaultSaltLen length.
|
|
||||||
|
|
||||||
If rounds is 0, BcryptPbkdfDefaultRounds will be used.
|
|
||||||
|
|
||||||
If keyLen is 0, BcryptPbkdfDefaultKeyLen will be used.
|
|
||||||
*/
|
|
||||||
func (b *BcryptPbkdf) Setup(secret, salt []byte, rounds, keyLen uint32) (err error) {
|
|
||||||
|
|
||||||
if secret == nil || len(secret) == 0 {
|
|
||||||
err = ErrNoSecret
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if salt == nil || len(salt) == 0 {
|
|
||||||
salt = make([]byte, BcryptPbkdfDefaultSaltLen)
|
|
||||||
if _, err = rand.Read(salt); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rounds == 0 {
|
|
||||||
rounds = BcryptPbkdfDefaultRounds
|
|
||||||
}
|
|
||||||
|
|
||||||
if keyLen == 0 {
|
|
||||||
keyLen = BcryptPbkdfDefaultKeyLen
|
|
||||||
}
|
|
||||||
|
|
||||||
b.secret = secret
|
|
||||||
b.salt = salt
|
|
||||||
b.rounds = rounds
|
|
||||||
b.keyLen = keyLen
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
SetupAuto is used to provide out-of-band configuration if the KDF options were found via GetKdfFromBytes.
|
|
||||||
|
|
||||||
You can test this by running KDF.AutoOK.
|
|
||||||
*/
|
|
||||||
func (b *BcryptPbkdf) SetupAuto(secret []byte, keyLen uint32) (err error) {
|
|
||||||
|
|
||||||
if !b.AutoOK() {
|
|
||||||
err = ErrUnknownKdf
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if keyLen == 0 {
|
|
||||||
keyLen = BcryptPbkdfDefaultKeyLen
|
|
||||||
}
|
|
||||||
|
|
||||||
b.secret = secret
|
|
||||||
b.keyLen = keyLen
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
DeriveKey returns the derived key from a BcryptPbkdf.
|
|
||||||
|
|
||||||
It must be called *after* Setup.
|
|
||||||
*/
|
|
||||||
func (b *BcryptPbkdf) DeriveKey() (key []byte, err error) {
|
|
||||||
|
|
||||||
if b.secret == nil {
|
|
||||||
err = ErrNoSecret
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if b.salt == nil {
|
|
||||||
err = ErrNoSalt
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if b.rounds == 0 {
|
|
||||||
err = ErrNoRounds
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if b.keyLen == 0 {
|
|
||||||
err = ErrNoKeyLen
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.key, err = bcryptPbkdf.Key(b.secret, b.salt, int(b.rounds), int(b.keyLen)); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key = b.key
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns BcryptPbkdfName.
|
|
||||||
func (b *BcryptPbkdf) Name() (name string) {
|
|
||||||
|
|
||||||
name = BcryptPbkdfName
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NameBytes returns the byte form of BcryptPbkdf.Name with leading bytecount allocator.
|
|
||||||
func (b *BcryptPbkdf) NameBytes() (name []byte) {
|
|
||||||
|
|
||||||
var nb []byte
|
|
||||||
var s = b.Name()
|
|
||||||
|
|
||||||
nb = []byte(s)
|
|
||||||
|
|
||||||
name = make([]byte, 4)
|
|
||||||
binary.BigEndian.PutUint32(name, uint32(len(nb)))
|
|
||||||
name = append(name, nb...)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// PackedBytes returns 3.0 and recursed.
|
|
||||||
func (b *BcryptPbkdf) PackedBytes() (buf *bytes.Reader, err error) {
|
|
||||||
|
|
||||||
var rounds = make([]byte, 4)
|
|
||||||
var packer *bytes.Reader
|
|
||||||
var w = new(bytes.Buffer)
|
|
||||||
|
|
||||||
// 3.0.0.0 and 3.0.0.0.0
|
|
||||||
if packer, err = internal.ReadSizeBytes(b.salt, true); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = packer.WriteTo(w); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3.0.0.1
|
|
||||||
binary.BigEndian.PutUint32(rounds, b.rounds)
|
|
||||||
if _, err = w.Write(rounds); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3.0
|
|
||||||
if buf, err = internal.ReadSizeBytes(w, true); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rounds returns the number of rounds used in derivation.
|
|
||||||
func (b *BcryptPbkdf) Rounds() (rounds uint32) {
|
|
||||||
|
|
||||||
rounds = b.rounds
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Salt returns the salt bytes.
|
|
||||||
func (b *BcryptPbkdf) Salt() (salt []byte) {
|
|
||||||
|
|
||||||
salt = b.salt
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
AutoOK returns true if a GetKdfFromBytes call was able to fetch the KDF options successfully, in which case the caller may use KDF.SetupAuto.
|
|
||||||
|
|
||||||
If false, it will need to be manually configured via KDF.Setup.
|
|
||||||
*/
|
|
||||||
func (b *BcryptPbkdf) AutoOK() (ok bool) {
|
|
||||||
|
|
||||||
ok = true
|
|
||||||
|
|
||||||
if b.salt == nil || len(b.salt) == 0 {
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.rounds == 0 {
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPlain indicates if this KDF actually does derivation (false) or not (true).
|
|
||||||
func (b *BcryptPbkdf) IsPlain() (plain bool) {
|
|
||||||
|
|
||||||
plain = false
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// addSalt adds a salt as parsed from GetKdfFromBytes.
|
|
||||||
func (b *BcryptPbkdf) addSalt(salt []byte) (err error) {
|
|
||||||
|
|
||||||
if salt == nil || len(salt) == 0 {
|
|
||||||
err = ErrNoSalt
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.salt = salt
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// addRounds adds the rounds as parsed from GetKdfFromBytes
|
|
||||||
func (b *BcryptPbkdf) addRounds(rounds uint32) (err error) {
|
|
||||||
|
|
||||||
if rounds == 0 {
|
|
||||||
err = ErrNoRounds
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b.rounds = rounds
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,137 +0,0 @@
|
|||||||
package kdf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Setup must be called before DeriveKey. It configures a Null.
|
|
||||||
|
|
||||||
Note that this doesn't actually do anything, it's here for interface compat.
|
|
||||||
It is recommended to use nil/zero values.
|
|
||||||
*/
|
|
||||||
func (n *Null) Setup(secret, salt []byte, rounds, keyLen uint32) (err error) {
|
|
||||||
|
|
||||||
_, _, _, _ = secret, salt, rounds, keyLen
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
SetupAuto is used to provide out-of-band configuration if the KDF options were found via GetKdfFromBytes.
|
|
||||||
|
|
||||||
Note that this doesn't actually do anything, it's here for interface compat.
|
|
||||||
It is recommended to use nil/zero values.
|
|
||||||
*/
|
|
||||||
func (n *Null) SetupAuto(secret []byte, keyLen uint32) (err error) {
|
|
||||||
|
|
||||||
_, _ = secret, keyLen
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
DeriveKey returns the derived key from a Null.
|
|
||||||
|
|
||||||
Note that this doesn't actually do anything, it's here for interface compat.
|
|
||||||
key will ALWAYS be a nil byte slice.
|
|
||||||
*/
|
|
||||||
func (n *Null) DeriveKey() (key []byte, err error) {
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns NullName.
|
|
||||||
func (n *Null) Name() (name string) {
|
|
||||||
|
|
||||||
name = NullName
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NameBytes returns the byte form of Null.Name with leading bytecount allocator.
|
|
||||||
func (n *Null) NameBytes() (name []byte) {
|
|
||||||
|
|
||||||
var b []byte
|
|
||||||
var s = n.Name()
|
|
||||||
|
|
||||||
b = []byte(s)
|
|
||||||
|
|
||||||
name = make([]byte, 4)
|
|
||||||
binary.BigEndian.PutUint32(name, uint32(len(b)))
|
|
||||||
name = append(name, b...)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// PackedBytes returns 3.0 and recursed.
|
|
||||||
func (n *Null) PackedBytes() (buf *bytes.Reader, err error) {
|
|
||||||
|
|
||||||
// This is static.
|
|
||||||
buf = bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Rounds returns the number of rounds used in derivation.
|
|
||||||
|
|
||||||
Note that this will always return 0; it's here for interface compat.
|
|
||||||
*/
|
|
||||||
func (n *Null) Rounds() (rounds uint32) {
|
|
||||||
|
|
||||||
rounds = 0
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Salt returns the salt bytes.
|
|
||||||
|
|
||||||
Note that this will always return nil; it's here for interface compat.
|
|
||||||
*/
|
|
||||||
func (n *Null) Salt() (salt []byte) {
|
|
||||||
|
|
||||||
salt = nil
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
AutoOK returns true if a GetKdfFromBytes call was able to fetch the KDF options successfully, in which case the caller may use KDF.SetupAuto.
|
|
||||||
|
|
||||||
If false, it will need to be manually configured via KDF.Setup.
|
|
||||||
|
|
||||||
Note that this won't actually do anything and ok will always return as true.
|
|
||||||
*/
|
|
||||||
func (n *Null) AutoOK() (ok bool) {
|
|
||||||
|
|
||||||
ok = true
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPlain indicates if this KDF actually does derivation (false) or not (true).
|
|
||||||
func (n *Null) IsPlain() (plain bool) {
|
|
||||||
|
|
||||||
plain = true
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// addSalt is a no-op, just here for interface compat.
|
|
||||||
func (n *Null) addSalt(salt []byte) (err error) {
|
|
||||||
|
|
||||||
_ = salt
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// addRounds is a no-op; just here for interface compat.
|
|
||||||
func (n *Null) addRounds(rounds uint32) (err error) {
|
|
||||||
|
|
||||||
_ = rounds
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
5
kdf/null/consts.go
Normal file
5
kdf/null/consts.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package null
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name string = "none"
|
||||||
|
)
|
137
kdf/null/funcs.go
Normal file
137
kdf/null/funcs.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package null
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Setup must be called before DeriveKey. It configures a null.KDF.
|
||||||
|
|
||||||
|
Note that this doesn't actually do anything, it's here for interface compat.
|
||||||
|
It is recommended to use nil/zero values.
|
||||||
|
*/
|
||||||
|
func (k *KDF) Setup(secret, salt []byte, rounds, keyLen uint32) (err error) {
|
||||||
|
|
||||||
|
_, _, _, _ = secret, salt, rounds, keyLen
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
SetupAuto is used to provide out-of-band configuration if the null.KDF options were found via kdf.UnpackKDF.
|
||||||
|
|
||||||
|
Note that this doesn't actually do anything, it's here for interface compat.
|
||||||
|
It is recommended to use nil/zero values.
|
||||||
|
*/
|
||||||
|
func (k *KDF) SetupAuto(secret []byte, keyLen uint32) (err error) {
|
||||||
|
|
||||||
|
_, _ = secret, keyLen
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
DeriveKey returns the derived key from a null.KDF.
|
||||||
|
|
||||||
|
Note that this doesn't actually do anything, it's here for interface compat.
|
||||||
|
key will ALWAYS be a nil byte slice.
|
||||||
|
*/
|
||||||
|
func (k *KDF) DeriveKey() (key []byte, err error) {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns null.Name.
|
||||||
|
func (k *KDF) Name() (name string) {
|
||||||
|
|
||||||
|
name = Name
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameBytes returns the byte form of null.Name with leading bytecount allocator.
|
||||||
|
func (k *KDF) NameBytes() (name []byte) {
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
var s = k.Name()
|
||||||
|
|
||||||
|
b = []byte(s)
|
||||||
|
|
||||||
|
name = make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(name, uint32(len(b)))
|
||||||
|
name = append(name, b...)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackedBytes returns block 3.0 and recursed.
|
||||||
|
func (k *KDF) PackedBytes() (buf *bytes.Reader, err error) {
|
||||||
|
|
||||||
|
// This is static.
|
||||||
|
buf = bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Rounds returns the number of rounds used in derivation.
|
||||||
|
|
||||||
|
Note that this will always return 0; it's here for interface compat.
|
||||||
|
*/
|
||||||
|
func (k *KDF) Rounds() (rounds uint32) {
|
||||||
|
|
||||||
|
rounds = 0
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Salt returns the salt bytes.
|
||||||
|
|
||||||
|
Note that this will always return nil; it's here for interface compat.
|
||||||
|
*/
|
||||||
|
func (k *KDF) Salt() (salt []byte) {
|
||||||
|
|
||||||
|
salt = nil
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
AutoOK returns true if a kdf.UnpackKDF call was able to fetch the null.KDF options successfully, in which case the caller may use null.KDF.SetupAuto.
|
||||||
|
|
||||||
|
If false, it will need to be manually configured via null.KDF.Setup.
|
||||||
|
|
||||||
|
Note that this won't actually do anything and ok will always return as true.
|
||||||
|
*/
|
||||||
|
func (k *KDF) AutoOK() (ok bool) {
|
||||||
|
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPlain indicates if this kdf.KDF actually does derivation (false) or not (true).
|
||||||
|
func (k *KDF) IsPlain() (plain bool) {
|
||||||
|
|
||||||
|
plain = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSalt is a no-op, just here for interface compat.
|
||||||
|
func (k *KDF) AddSalt(salt []byte) (err error) {
|
||||||
|
|
||||||
|
_ = salt
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRounds is a no-op; just here for interface compat.
|
||||||
|
func (k *KDF) AddRounds(rounds uint32) (err error) {
|
||||||
|
|
||||||
|
_ = rounds
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
4
kdf/null/types.go
Normal file
4
kdf/null/types.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package null
|
||||||
|
|
||||||
|
// KDF is a dummy kdf.KDF that is used for unencrypted/plain SSH private keys. It literally does nothing.
|
||||||
|
type KDF struct{}
|
31
kdf/types.go
31
kdf/types.go
@ -26,31 +26,8 @@ type KDF interface {
|
|||||||
IsPlain() (plain bool)
|
IsPlain() (plain bool)
|
||||||
// PackedBytes returns the bytes suitable for serializing into a key file.
|
// PackedBytes returns the bytes suitable for serializing into a key file.
|
||||||
PackedBytes() (buf *bytes.Reader, err error)
|
PackedBytes() (buf *bytes.Reader, err error)
|
||||||
// addSalt adds the salt as parsed from the private key.
|
// AddSalt adds the salt as parsed from the private key.
|
||||||
addSalt(salt []byte) (err error)
|
AddSalt(salt []byte) (err error)
|
||||||
// addRounds adds the rounds as parsed from the private key.
|
// AddRounds adds the rounds as parsed from the private key.
|
||||||
addRounds(rounds uint32) (err error)
|
AddRounds(rounds uint32) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
BcryptPbkdf combines bcrypt hashing algorithm with PBKDF2 key derivation.
|
|
||||||
|
|
||||||
(bcrypt) https://www.usenix.org/legacy/events/usenix99/provos/provos_html/node1.html
|
|
||||||
(PBKDF2) https://datatracker.ietf.org/doc/html/rfc2898
|
|
||||||
http://www.tedunangst.com/flak/post/bcrypt-pbkdf
|
|
||||||
*/
|
|
||||||
type BcryptPbkdf struct {
|
|
||||||
// salt is used to salt the hash for each round in rounds.
|
|
||||||
salt []byte
|
|
||||||
// rounds controls how many iterations of salting/hashing is done.
|
|
||||||
rounds uint32
|
|
||||||
// keyLen is how long the derived key should be in bytes.
|
|
||||||
keyLen uint32
|
|
||||||
// secret is the "passphrase" used to seed the key creation.
|
|
||||||
secret []byte
|
|
||||||
// key is used to store the derived key.
|
|
||||||
key []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Null is a dummy KDF that is used for unencrypted/plain SSH private keys. It literally does nothing.
|
|
||||||
type Null struct{}
|
|
||||||
|
@ -6,14 +6,14 @@ import (
|
|||||||
`r00t2.io/sshkeys/kdf`
|
`r00t2.io/sshkeys/kdf`
|
||||||
)
|
)
|
||||||
|
|
||||||
// KeyEd25519 is an ed25519 OpenSSH key.
|
// Key is an ed25519 OpenSSH key.
|
||||||
type KeyEd25519 struct {
|
type Key struct {
|
||||||
KeyPairs []*KeypairEd25519 `xml:"keypairs" json:"keypairs" yaml:"Keypairs"`
|
KeyPairs []*Keypair `xml:"keypairs" json:"keypairs" yaml:"Keypairs"`
|
||||||
Cipher string // TODO: (sshkeys/cipher).Cipher
|
Cipher string // TODO: (sshkeys/cipher).Cipher
|
||||||
KDF kdf.KDF
|
KDF kdf.KDF
|
||||||
}
|
}
|
||||||
|
|
||||||
type KeypairEd25519 struct {
|
type Keypair struct {
|
||||||
Private ed25519.PrivateKey `xml:"private" json:"private_key" yaml:"Private Key"`
|
Private ed25519.PrivateKey `xml:"private" json:"private_key" yaml:"Private Key"`
|
||||||
Public ed25519.PublicKey `xml:"public" json:"public_key" yaml:"Public Key"`
|
Public ed25519.PublicKey `xml:"public" json:"public_key" yaml:"Public Key"`
|
||||||
Comment string `xml:"comment,attr" json:"comment" yaml:"comment"`
|
Comment string `xml:"comment,attr" json:"comment" yaml:"comment"`
|
||||||
|
Loading…
Reference in New Issue
Block a user